五种指令
普通调用指令:
- invokestatic:调用静态方法
- invokespecial:调用私有实例方法、构造器,和父类的实例方法或构造器,以及所实现接口的默认方法
- invokevirtual:调用所有虚方法(虚方法分派)
- invokeinterface:调用接口方法
动态调用指令:
- invokedynamic:动态解析出需要调用的方法
- Java7 为了实现动态类型语言支持而引入了该指令,但是并没有提供直接生成 invokedynamic 指令的方法,需要借助 ASM 这种底层字节码工具来产生 invokedynamic 指令
- Java8 的 lambda 表达式的出现,invokedynamic 指令在 Java 中才有了直接生成方式
指令对比:
- 普通调用指令固化在虚拟机内部,方法的调用执行不可干预,根据方法的符号引用链接到具体的目标方法
- 动态调用指令支持用户确定方法
- invokestatic 和 invokespecial 指令调用的方法称为非虚方法,虚拟机能够直接识别具体的目标方法
- invokevirtual 和 invokeinterface 指令调用的方法称为虚方法,虚拟机需要在执行过程中根据调用者的动态类型来确定目标方法
指令说明:
- 如果虚拟机能够确定目标方法有且仅有一个,比如说目标方法被标记为 final,那么可以不通过动态绑定,直接确定目标方法
- 普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
符号引用
在编译过程中,虚拟机并不知道目标方法的具体内存地址,Java 编译器会暂时用符号引用来表示该目标方法,这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符
- 对于静态绑定的方法调用而言,实际引用是一个指向方法的指针
- 对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引
符号引用存储在方法区常量池中,根据目标方法是否为接口方法,分为接口符号引用和非接口符号引用:
Constant pool: ... #16 = InterfaceMethodref #27.#29 // 接口 ... #22 = Methodref #1.#33 // 非接口 ...
对于非接口符号引用,假定该符号引用所指向的类为 C,则 Java 虚拟机会按照如下步骤进行查找:
- 在 C 中查找符合名字及描述符的方法
- 如果没有找到,在 C 的父类中继续搜索,直至 Object 类
- 如果没有找到,在 C 所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。如果有多个符合条件的目标方法,则任意返回其中一个
对于接口符号引用,假定该符号引用所指向的接口为 I,则 Java 虚拟机会按照如下步骤进行查找:
- 在 I 中查找符合名字及描述符的方法
- 如果没有找到,在 Object 类中的公有实例方法中搜索
- 如果没有找到,则在 I 的超接口中搜索,这一步的搜索结果的要求与非接口符号引用步骤 3 的要求一致