1. 动态分派
成都创新互联服务项目包括神木网站建设、神木网站制作、神木网页制作以及神木网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,神木网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到神木省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!一个体现是重写(override)。下面的代码,运行结果很明显。
1 public class App { 2 3 public static void main(String[] args) { 4 Super object = new Sub(); 5 object.f(); 6 } 7 } 8 9 class Super {10 public void f() {11 System.out.println("super : f()");12 }13 14 public void f(int i) {15 System.out.println("super : f(int)");16 }17 }18 19 class Sub extends Super{20 21 @Override22 public void f() {23 System.out.println("sub : f()");24 }25 26 @Override27 public void f(int i) {28 System.out.println("sub : f(int)");29 }30 31 public void f(char c) {32 System.out.println("sub : f(char)");33 }34 }
最终输出sub : f();
那么虚拟机是怎么做到动态分派的呢?
不同的虚拟机有不同的实现,最常用的是使用虚方法表(Virtual Method Table)
2. 虚方法表
对于Super和Sub类,虚方法表大致如下:(灵魂画师)
上面的灵魂画作是什么意思呢?
虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同签名的方法的地址入口是一致的,都指向父类的实现入口。如果子类中重写了这个方法,子类方法表中的地址将会替换为向子类实现版本的入口地址。
从上图主要得出几个信息:
a. 上图的大部分方法,子类Super和Sub均没有重写,那么都指向父类Object的类型数据。f()和f(int)方法,父类子类都实现了,那么两者就指向不同的实现地址。f(char)只在子类定义实现,自然指向子类的类型数据。
b. 为了程序实现上的方便,具有相同签名的方法,在父类,子类的虚方法表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需要的入口地址。
3. 实例分析
以本文开头的代码进行分析。通过javap命令查看main方法的指令。
其中的invokevirtual指令详细调用过程是这样的:
1)指令中的#19指的是App类的常量池中第19个常量表的索引项。这个常量表(CONSTATN_Methodref_info)记录的是方法f()信息的符号引用,JVM首先根据这个符号引用找到调用方法f()的类的全限定名com.khlin.Super,这是因为变量object被声明为Super类型。
2) 在Super类型的方法表中查找方法f(),如果找到,则将方法f()在方法表中的索引项(具体值我不了解,这里将其记为index) 记录到App类的常量池中第19个常量表中(常量池解析)。因此,如果Super类型方法表中没有f(),那么即使Sub类型的方法表有该方法,也会报编译失败。
3)在调用invokevirtual指令前有一个aload_1指令,它会将开始创建中堆中的Sub对象的引用压入操作数栈。然后invokevirtual指令会根据这个Sub对象的引用首先找到堆中的Sub对象,然后进一步找到Sub对象所属类型的方法表。
4)这时,通过2)查找的index,可以定位到Sub类型方法表中的f()方法,然后通过直接地址找到该方法字节码所在的内存空间。这就是父类和子类相同签名的方法索引序号一致的用处。
4. 综合考虑:一个可能想错的例子
将本文开头的代码里的main方法稍作修改,调用其他的方法。
1 public static void main(String[] args) {2 Super object = new Sub();3 char c = 'a';4 object.f(c);5 }
结果将输出sub : f(int)
明明Sub方法里有完全一样类型的f(char)方法,却调用的是f(int).
相信通过前面的学习,已经可以明白原因了。
在object.f(c)调用时,虚拟机先到Super类的方法表里,查找最为合适的方法。
Super类里没有刚好参数为char的f(char)方法,按照前面静态分派和参数类型自动转换的学习,可以知道,编译器使用了除了f(char)之外最为合适的方法f(int)。获取到索引后,通过索引到实际对象的Sub方法表里找到f(int)方法,最终执行的就是Sub类的f(int)方法。
该方法的字节码指令证明了上述的论证。
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。
当前文章:JVM方法调用之动态分派-创新互联
网页链接:http://lswzjz.com/article/dcseop.html