栈帧的内部结构--动态链接 (Dynamic Linking)
每个栈帧中包含:
- 局部变量表(Local Variables)
- 操作数栈(Opreand Stack) 或表达式栈
- 动态链接 (Dynamic Linking) (或指向运行时常量的方法引用)
- 动态返回地址(Return Address) (或方法正常退出或者异常退出的引用的定义)
- 一些附加信息
动态链接
-
动态链接主要就是指向运行时常量池的方法引用
- 每一个栈帧内存都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如invokedynamic 指令
- 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference )保存在class文件的常量池里。比如,描述一个方法调用其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
1、方法的调用
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制有关
方法的静态链接与动态链接
- 静态链接:当一个字节码文件被装在进JVM内存时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接而引用的过程称之为静态链接。 例如,super()方法
- 动态链接:如果被调用的方法无法在编译期确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程中具备动态性,因此也被称之为动态链接。对应着接口回调,多态动态绑定等
与之对应的则是方法的绑定机制。早期绑定(Early Binding)和晚期绑定(late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这里仅仅发生一次。
- 早期绑定:早期绑定就是被调用的目标函数如果在编译期可知,且运行期间保持不变,即可将这个方法与所属的类型进行绑定。
- 晚期绑定:如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型确定相关的方法,被称之为晚期绑定。其实也就是动态绑定
2、虚方法与非虚方法
- 对应着进行早期绑定和静态链接的定义,即在编译期就确定了具体的调用版本,在运行时不可变,称之为非虚方法
- 静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法
- 其他方法称之为虚方法
- 子类对象的多态性使用的前提为:类的继承关系,方法的重写
-
可以简单的理解为自己写的方法就是虚方法。
- 在面向对象的编程中,会很频繁的使用到动态分派,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话可能影响到执行效率,因此,为了提高性能,JVM采用在类的方法区建立一个虚方法表(virtual method table)(非虚方法不会出现在表中)来实现,使用索引表来替代查找
- 每个类中都有一个虚方法表,表中存放着各个方法的实际入口
- 虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成以后,JVM会把该类的方法表也初始化完毕
3、方法的调用指令
- 普通调用指令
- invokestatic:调用静态方法,解析阶段确定唯一方法版本
- invokesopecial:调用<init>方法、私有即父类犯法,解析阶段确定唯一方法版本
- invokevirtual:调用所有虚方法
- invokeinterface:调用接口方法
- 动态调用指令:
- invokedynamic:动态解析出所有需要的方法,然后执行,(lamble表达式),和python一样,变量不需要自己执行,运行时才知道
4、方法重写的本质
- 找到操作数栈顶的第一个元素所执行的对象的实际类型,记作C
- 如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则放回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.illegalAccessError异常
- 否则,按照继承关系从上往下依次对C的各个父类进行第2步的搜索和验证过程
- 如果始终没有找到合适的方法,则抛出java.lang.AbstactMethodError异常