JVM学习笔记六:字节码执行引擎

运行时栈帧结构

栈帧是虚拟机运行时数据区中的虚拟机栈的栈元素,栈帧存储了方法的局部变量表,操作数栈,动态连接,方法返回地址,在编译程序代码的时候,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中。
jinjiprojectnaotu

局部变量表

其最大容量由方法的Code属性,max_locals确定,局部变量表的容量以变量槽(Slot)为最小单位,Long和Double占用两个空间,其读写是非原子性的,其会占用n与n+1两个Slot,不允许采用任何方式单独访问其中的一个Slot。

在方法执行时,虚拟机是使用局部变量表来完成参数值到变量表的传递,如果执行的是实例方法,那么局部变量表第0位索引的Slot默认是用于传递方法所属的对象实例的引用,其余参数从索引1开始占用Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot,为了尽可能的节省栈帧空间,局部变量表中的Solt是可以重用的,当PC计数器的值,已经超出方法体中定义的变量的作用域时,其占用的Slot会被其他作用域的变量重用。

操作数栈

其最大深度由方法的Code属性的max_stacks决定,有些虚拟机实现上,会让相邻的两个栈帧出现一些 共享部分,方便方法调用之间共享数据。

动态连接

每个栈帧都包含一个指向运行时常量池中该帧所属方法的引用,这个引用是为了支持方法调用过程中的动态连接。

方法返回地址

当一个方法执行以后,只有两种方式可以退出这个方法,一个是执行引擎遇到一个返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,这种退出方法被称为正常完成出口,另一种退出方式是,在方法的执行过程中遇到了异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式被称为异常完成出口,这种方式是不会给它的上层调用者返回任何返回值的。

无论任何方式退出,都需要返回到方法被调用的位置,程序才能继续执行,所以方法退出时会恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈,调整PC至下一条指令地址。

方法调用

方法调用阶段唯一的任务就是确定被调用方法的版本

方法调用的方式

方法的调用包括解析与分派两种方式

  1. 解析可以在类加载的解析阶段,将符号引用直接转化为直接引用,这种解析成立的前提是,方法的运行之前就可以确定调用的版本是唯一的,而且是在运行期不可变的。
  2. 分派又分为静态分派与动态分派,重载机制就是由静态分派实现,而重写则是由动态分派实现。

动态分派的解析过程(invokevirtual指令为例):

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记做C。
  2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束,否则,返回java.lang.IllegalAccessError
  3. 否则,按照继承关系从下往上对C的各个父类进行第2步的搜索和验证过程。
  4. 如果没有找到合适的方法,抛出java.lang.AbstractMethodError异常。

出于性能的考虑,会为类在方法区建立一个虚方法表,使用虚方法表索引来代替元数据查找以提高性能
jinjiprojectnaotu

参考资料

本文参考:《深入理解Java虚拟机》

原文地址:https://www.cnblogs.com/MinnieChang/p/7337413.html