Java虚拟机运行时栈帧结构
读《深入理解Java虚拟机》第三版,周志明著。
官方文档 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6
概要说明
Java 虚拟机以方法作为最基本的执行单元,“ 栈帧 ” (Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
每一个方法从调用开始至执行结束的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
每一个栈帧包含了局部变量表
、操作数栈
、动态连接
、方法返回地址
和一些额外的附加信息。
结构如下:
1、局部变量表
局部变量表(Local Variable Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
在 Java 程序被编译为 Class 文件时,就在方法的 Code 属性的 max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量。
为了尽可能节省栈帧耗用的内存空间,局部变量表中的变量槽是可以重用的。
局部变量定义了,但是没有赋初始值,那它是完全不能使用的。
2、操作数栈
操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。
用局部变量表一样,操作数栈的最大深度也是在编译的时候被写入到 Code 属性的 max_stacks 数据项之中。
当一个方法刚刚开始执行时,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈。譬如,在做算术运算时,通过将运算涉及的操作数压入栈顶后,调用用运算指令。又譬如,调用其他方法时,通过操作数栈来进行运行参数的传递。
操作数栈中的数据类型必须与字节码指令的序列严格匹配。在编译时保证,在类加载时校验。
Java 虚拟机的解释执行引擎被称为 “ 基于栈
的执行引擎 ”,里面的 “ 栈 ” 就是操作栈。
3、动态连接
每个栈帧都包含,一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
Class 文件的常量池中存在大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这个符号引用,一部分在类加载阶段转为直接引用,称为静态解析。剩下的部分每次运行期间转为直接引用,就是动态连接。
4、返回地址
当一个方法开始执行后,只有两种方式退出这个方法。
(1)正常调用完成(Normal Method Invocation Completion)
执行引擎遇到任意一个方法返回的字节码指令,返回方法调用者。这种通过方法返回指令来决定返回的方式,叫做正常调用完成。
(2)异常调用完成(Abrupt Method Invocation Completion)
执行引擎遇见异常,没有在本方法的异常表中匹配到这种异常,然后导致的方法返回,叫做异常调用完成。
不过通过那种方式退出,都会回到最初方法被调用的位置,所以需要在栈帧中保存一些信息,用于帮助恢复上层主调方法的执行。