Java 中的方法识别
在 Java 里有重载和重写的概念,简单地说,重载是在同一个类中的多个同名但参数类型或个数不同的方法;重写是子类中定义了与父类同名且同参数类型和个数的非私有方法,是体现多态的一种方式,另外,如果方法在父类和子类是静态的,那么我们可以说是隐藏了父类的方法。
和重写在运行时动态识别目标方法不同,重载在编译时就能完成识别,这点在阅读代码时进行方法跳转时就能体会到。Java 会根据传入参数的声明类型按一下步骤依次选取重载方法:
- 先不考虑基本类型的自动拆装箱,不考虑可变长参数;
- 考虑基本类型的自动拆装箱,不考虑可变长参数;
- 考虑基本类型的自动拆装箱,考虑可变长参数;
JVM 中的方法识别与调用
调用指令
调用指令是 JVM 在调用方法时所要运行的命令,分别有 invokestatic、invokespecial、invokevirtual、invokeInterface 和 invokedynamic 五种。
静态绑定和动态绑定
JVM 在方法的识别上没有重写和重载的概念,而有静态绑定和动态绑定的的区别。解析方法时可以直接确认到目标方法叫静态绑定,相反地,需要在运行时根据调用者类型动态识别的叫动态绑定。
调用指令和静、动态绑定的关系
一般地,invokestatic 和 invokespecial 这两种指令对应的方法是静态绑定的,其中 invokestatic 对应的是静态方法,而 invokespecial 对应的是类的实例的私有方法、final 修饰的实例的非私有方法、构造器方法、Super 调用父类的实例方法和构造器,以及所实现接口的默认方法。很明显,这七种方法可以直接确定实际执行的方法的实现。
另外有两种方法无法直接确定实际执行的实现,它们是 invokevirtual 对应的非私有实例方法和 invokeinterface 对应的接口方法,所以,这两种方法是动态绑定的。
符号引用
方法所在的类在被加载进虚拟机前,其方法还没有被分配实际的内存地址,为了以后方便定位到方法,编译器会暂时用符号引用来表示目标方法。符号引用包括方法所在的类名或接口名、方法名和方法描述符,其中方法描述符由方法的参数类型和返回类型组成。
在类加载的解析阶段,虚拟机会将符号引用解析为实际引用。首先是根据符号引用查找到目标方法,其过程针对符号接口引用 InterfaceMethodref 和非符号接口引用 Methodref 分为两种过程:
符号引用 InterfaceMethodref
在当前接口中查找
在 Object 中查找 public 实例方法
在超接口中查找非私有非静态的方法
非符号引用 Methodref
在当前类中查找
在父类中查找,直到 Object 类
在实现的接口中查找非私有非静态的方法
查找到目标方法后便可以转化为实际引用了。这时根据静、动态绑定的方法调用又分为两种结果:如果目标方法调用是静态绑定的,则得到为一个指向该目标方法的指针;如果是动态绑定的目标方法调用,则是一个方法表的索引。
执行时只要根据指针指向的地址,或者根据调用者类型查找方法表,就可以定位到目标方法的实现了,也就完成了方法的调用。
总结
需要先识别到目标方法上,才能完成方法的调用和执行。在 Java 层面有重载和重写的概念,到了 JVM 层面则是静态绑定和动态绑定的区别,本文只是从这两个角度对方法识别机制和静态绑定的方法调用进行了简述,并涉及到调用指令、符号引用及其解析等方面的知识,弄懂这些机制的同时,还有动态绑定的方法调用的机制需要我们去了解。