Java及JVM是如何识别重载、重写方法的?(下)

调用指令的符号引用

编译过程中,我们并不知目标方法的具体内存地址。因此,Java编译器会暂时用符号引表示该目标方法。

这符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。


符号引用存储在class文件的常量池。根据目标方法是否为接口方法,这些引用可分为:


  • 接口符号引用
  • 非接口符号引用
// 在奸商.class的常量池中,#16为接口符号引用,指向接口方法"客户.isVIP()"。#22为非接口符号引用,指向静态方法"奸商.价格歧视()"。
$ javap -v 奸商.class ...
Constant pool:
...
  #16 = InterfaceMethodref #27.#29        // 客户.isVIP:()Z
...
  #22 = Methodref          #1.#33         // 奸商.价格歧视:()D
...

执行使用了符号引用的字节码前,JVM需解析这些【符号引用】并替换为【实际引用】。


对【非接口符号引用】,假定该【符号引用】所指向的类为C,则JVM按如下步骤查找:


  • 在C中查找符合名字及描述符的方法
  • 若没找到,搜索C的父类,直至Object类
  • 若还没找到,在C所直接实现或间接实现的接口中搜索,该步搜索得到的目标方法必须是非private、非static且若目标方法在间接实现的接口中,则需满足C与该接口间无其他符合条件的目标方法。若有多个符合条件的目标方法,则返回其中任一。


所以static方法也可通过子类来调用。子类的static方法会隐藏(这不是重写)父类中的同名、同描述符的静态方法。


对于接口符号引用,假定该符号引用所指向的接口为I,则Java虚拟机会按照如下步骤进行查找。


  1. 在I中查找符合名字及描述符的方法。
  2. 如果没有找到,在Object类中的公有实例方法中搜索。
  3. 如果没有找到,则在I的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤3的要求一致。


经过上述解析步骤后,符号引用会被解析成实际引用:


  • 对可静态绑定的方法调用,实际引用是个指向方法的指针
  • 对需动态绑定的方法调用,实际引用则是个方法表的索引

总结与实践

本文介绍了Java以及Java虚拟机是如何识别目标方法的。

在Java方法的:


  • 重载,方法名相同而参数类型不相同的方法间
  • 重写,方法名相同&参数类型也相同的方法间


JVM识别方法的方式除了方法名和参数类型,还有返回类型。

JVM的:


  • 静态绑定:在解析时便能够直接识别目标方法的情况
  • 动态绑定,需在运行过程中根据调用者的动态类型来识别目标方法的情况。由于Java编译器已区分重载方法,因此可认为JVM不存在重载


在class文件中,Java编译器会用符号引用指代目标方法。在执行调用指令前,它所附带的符号引用需要被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用为目标方法的指针。对于需要动态绑定的方法调用而言,实际引用为辅助动态绑定的信息。


Java的重写与Java虚拟机中的重写并不一致,但编译器会通过生成桥接方法来弥补。



参考


上一篇:互联网时代下机械硬盘的发展史(上)


下一篇:Git切换分支时报错:you need to resolve your current index first