JVM虚拟机栈
动态链接(指向运行时常量池的方法引用)
- 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。比如invokedynamic指令。
- 在Java源文件被编译到字节码文件中,所有的变量和方法都作为符号引用保存在class文件的常量池里,比如:描述一个方法调用了其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接就是为了将这些符号引用转换为调用方法的直接引用。
帧数据区保存着能够访问常量池的指针。
public class DynamicLinkingTest{
public void methodA(){
System.out.println("methodA()...");
}
public void methodB(){
System.out.println("methodB()...");
methodA();
num++;
}
}
上述代码的方法属性均在常量池中保存。
为什么需要常量池
常量池的作用,就是为了提供一些符号和常量,便于指令的识别。
方法的调用
在JVM中将符号引用转化为直接引用与方法的绑定机制相关。
- 静态链接
- 当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变。这种情况下将调用方法的符号引用转换为直接引用的过程称为静态链接。
- 动态链接:
- 如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种转换过程具备动态性,因此就被称为动态链接。
方法的绑定机制:
- 早期绑定:
- 早期绑定即使指被调用的目标方法如果在编译器可知,且运行期保持不变时,即可将这个方法与所属的类型进行绑定,由于明确了被调用的目标方法究竟时哪一个,因此也就可以使用静态链接的方法将符号引用转化为直接引用
- 晚期绑定
- 如果被调用的方法在编译器无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方法也就称为晚期绑定。(invokeinterface,invekevirtual(除了final方法之外))
虚方法与非虚方法
- 非虚方法
- 方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法。
- 静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。
- 其他方法称为虚方法。
子类对象多态的使用前提:
- 类的继承关系
- 方法的重写
虚拟机中提供了以下几条方法调用指令
- 普通调用指令:
- invokestatic:调用静态方法,解析阶段确定唯一方法版本
- invokespecial:调用方法、私有以及父类方法,解析阶段确定唯一方法版本
- invokevirtual:调用所有虚方法
- invokeinterface:调用接口方法
invokestatic与incokespecial调用的方法都是非虚方法
- 动态调用指令
- invokedynamic:动态解析出需要调用的方法,然后执行。
上述4个方法为非虚方法
invokedynamic 指令的使用
- JVM字节码指令集在Java7中增加了一个invokedynamic指令,这是Java为了实现动态类型语言支持而做的一种改进。
- 但是Java7并没有提供直接生成invokedynamic指令的方法,需要借助ASM这种底层字节码工具来产生invokdynamic指令。直到Java8的Lambda表达式的出现,invokedynamic指令的生成,在Java中才有了直接生成方式。
- Java7中增加的动态语言类型支持的本质是对Java虚拟机规范的修改,而不是对Java语言规则的修改,这一块相对来讲比较复杂,增加了虚拟机中的方法调用,最直接的受益者就是运行在Java平台的动态语言的编译器。
Java语言方法重写的本质:
- 找到操作数栈顶的第一个元素所执行的实际类型,记作C。
- 如果在类型C中找到与常量中的描述符符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过,则返回。
- 否则,按照继承关系从下往上一次对C的各个父类继续宁第2步搜索和验证过程。
- 如果始终没有找到合适的方法,抛出java.lang.AbstractMethodError异常
- 在面向对象过程中,会很频繁的使用到动态分配,如果每次动态分配的过程中都要重新在类的方法元数据中搜索合适的目标的话可能影响执行效率。因此,为了提高性能,JVM采用在类的方法区建立一个虚方法表来实现,使用索引来代替查找。
- 每个类中都有一个虚方法表,表中存放着各个方法的实际入口。
- 虚方法表在类加载的链接阶段被创建并开始初始化,类的变量初始化准备完成之后,JVM会把该类的方法表也初始化完毕。
虚方法表如上图所示。
方法返回地址
- 存放调用该方法的PC寄存器的值
- 一个方法的结束,有两种方式
- 正常执行完成
- 出现未处理的异常,非正常退出
- 无论通过哪种方法退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。通过异常退出的,返回地址时通过异常表记录的,方法返回地址不会保存这个值。