方法调用
- 方法调用即确定调用方法的版本, 一切方法调用在
Class
文件存储都是符号引用; 而直接引用则是方法实际内存地址 - 方法调用指令
- invokevirtual指令, 用于调用对象的实例方法, 根据对象的实际类型进行分派(虚方法分派)
- invokeinterface指令, 用于调用接口方法
- invokespecial指令, 调用需要特殊处理的实例方法, 如实例初始化方法, 私有方法和父类方法
- invokestatic指令, 调用类方法
- invokedynamic指令, 用于运行时动态解析出调用点限定符所引用的方法, 并执行该方法, 前面4条指令的分派逻辑固化在Java虚拟机内部, 该方法由用户所设定的引导方法决定
解析
- 方法在
Class
文件中都是常量池中的符号引用, 解析是静态过程, 在编译期间可以完全确定, 在类装载的解析阶段把相关符号引用转化成直接引用
分派
- 静态分派
Human man = new Man()
,Human
为静态类型(外观类型), 而Man
为实际类型, 静态类型和实际类型可以发送变化, 但静态类型仅在使用时发生(如强制类型转换)- 动态类型变化结果在运行起可确定, 编译器对(对象的)实际类型无感
- 变量的静态类型不会变改变, 编译器在确定重载函数版本通过参数的静态类型而非实际类型, 编译期间即可确定重载版本; 根据上述结果可验证这一结论
public class Dispatch {
static abstract class Human {}
static class Man extends Human {}
static class Woman extends Human {}
public static void main(String args[]) {
Human man = new Man();
Human woman = new Woman();
Dispatch d = new Dispatch();
// 输出hello human
d.sayHello(man);
// 输出hello human
d.sayHello(woman);
}
public void sayHello(Human human) {
System.out.println("hello human");
}
public void sayHello(Man man) {
System.out.println("hello man");
}
public void sayHello(Woman woman) {
System.out.println("hello woman");
}
}
- 动态分派
- invokevirtual指令即把常量池的类方法符号引用解析到不同的直接引用上, 该过程即Java方法重写的本质;
invokevirtual
指令的多态查找过程: 1). 找到操作数栈顶元素锁指向的对象的实际类型, 记为C. 2). 如果在类型C中找到与常量中的描述符和简单名称都相符的方法, 则进行权限校验, 如果通过则返回这个方法的直接引用; 不通过则抛IllegalAccessException
异常 3). 否则, 按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程 4). 如果找不到合适的方法, 则抛AbstractMethodError
异常 - 虚拟机动态分派的实现, 在方法区执行时建立虚方法表(Virtual Method Table)
- invokevirtual指令即把常量池的类方法符号引用解析到不同的直接引用上, 该过程即Java方法重写的本质;
参考
- 图解JVM执行引擎之方法调用