前言
class文件被加载到内存中,JVM如何执行类方法?下面我们来看一下方法的人生。
重写与重载
重写与重载对与我们并不陌生,java作为面向对象的语言,有封装、继承、多态三大特性,而重写是多态的一个体现,而重载是参数不同的同名方法。
重载
但是对于JVM并没有重载这个概念,在源代码编译时就已经指定了实际的方法,那么指定的具体过程是怎么样的呢?编译器根据传入参数的声明类型(注意与实际类型区分)来选择重载,选取分为三个阶段:
- 不考虑自动拆装箱以及可变长参数的情况下选取方法
- 允许自动拆装箱,但是不允许可变长参数
- 允许自动拆装箱和可变长参数
重写
而重写会根据调用者的动态类型选取实际的目标方法。JVM识别方法主要通过类名、方法名、以及方法描述符(方法描述符由方法的参数类型以及返回类型构成)。JVM对于方法重写的判定也基于方法描述符,子类定义了与父类非私有、非静态的方法、当方法描述符相同时判定为重写。
方法的调用
字节码中与调用相关的指令有五种:
-
invokestatic:调用静态方法,
-
invokespecial:调用私有实例方法,构造器,以及supper调用父类的实例方法或构造器和所以实现接口的默认方法
-
invokevirtual:调用非私有实例方法
-
invokeinterfance:调用接口方法
-
invokedynamic:调用动态方法
编译过程中,不知道目标方法的内存地址,用符号引用来表示,符号引用包括目标方法的类或接口名字,以及目标方法的方法名和方法描述符,符号引用存入class文件的常量池中,根据目标方法分为接口引用和非接口引用,在执行解析时会将符号引用替换为实际引用。
非接口引用,假如符号引用指向类C:
- 在C中查找符号名字及描述符的方法
- 如果没有找到,C的父类中搜索,一直到Object
- 如果没有找到,C的直接实现或间接实现的接口搜索,这一步得到的目标方法是非私有非静态,如果目标方法在间接实现的接口中,则需满足C与该接口之间没有其他符合条件的目标方法,如果多个符符合,任意返回
接口引用,假设执行接口I:
- I中查找符合名字和描述符的方法
- 在Object的公有实例中搜索
- 在I的超接口中搜索,要求与非接口的引用一致
经过对符号引用的的解析,符号引用解析成了实际引用,可以静态绑定的方法,实际引用是指向方法的指针。需要动态绑定的方法调用,实际引用是一个方法表的索引。
方法表
在写类加载机制的那篇博客中曾经说过,在类加载的准备阶段有的虚拟机会生成一个类的方法表,这个方法表就是用来动态确定方法调用的。
方法表其实就是一个数组,每个元素都指向当前类或者祖先类中非私有的实例方法,但是方法表还需要满足两个条件:子类包含父类的所有方法,并且子类重写的方法与父类的方法索引要相同。在实际的调用过程中,JVM根据调用者的实际类型在方法表中获取目标方法
当然方法表作为定位目标方法的一种手段仅存在解析执行中,在即时编译下还有更好的方法比如:内联缓存、方法内联