运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用方法执行数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素。每个方法从调用开始执行完成的过程,都对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈帧存储了方法的局部变量表操作数栈动态连接方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表多深的操作数栈都已完全确定,并写入到方法表的Code属性中了。因此栈帧需要分配多少内存,不受运行期变量数据影响,仅取决于具体的虚拟机实现。

对于执行引擎来说,在活动线程中,只有当前栈帧(位于栈顶的栈帧)才有效,与该栈帧关联的方法称为当前方法。执行引擎运行的所有字节码指令都是针对当前栈帧进行操作。

栈帧的概念结构

局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在程序被编译成Class文件时,就在方法区的Code属性max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量

局部变量表容量以变量槽Slot为最小单位,一个Slot可存放一个32位以内的数据类型,Java中占用32位以内的数据类型有booleanbytecharshortintfloatreferencereturnAddress 8种类型,64位的数据类型只有longdouble两种。

reference类型表示对一个对象实例的引用,一是从此引用中直接或间接地查找到对象在Java堆中的数据存放的起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中存储的类型信息

局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写的Slot是否为原子操作,都不会引起数据安全问题

方法在执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程,若执行的是实例方法,局部变量表中第0位索引默认是用于传递方法所属对象实例的引用,可通过this关键字来访问该隐含参数。

类变量有两次赋初始值的过程,在准备阶段赋系统初始值,在初始化阶段赋定义初始值。但局部变量没有赋初始值就不能使用。

操作数栈

Java虚拟机解释执行引擎称为基于栈的执行引擎,其中的栈就是操作数栈,操作数栈也称为操作栈,是一个后入先出栈,最大深度在编译时写入Code属性max_stacks数据项中。操作数栈的每个元素可以是任意Java数据类型,包括long和double,32数据类型占1个栈容量64数据类型占2个栈容量操作数栈中元素的数据类型必须与字节码指令序列严格匹配

方法开始执行时,该方法的操作数栈为空,方法在执行过程中,会有各种字节码指令往操作数栈中写入提取内容,也就是入栈和出栈操作。在算数运算时通过字节码指令将最接近栈顶的两个元素出栈并进行计算,然后将计算结果入栈,在调用其他方法时通过操作数栈来进行参数传递

概念模型中两个栈帧作为虚拟机的元素是完全独立的,但大多数虚拟机都进行了一些优化,令两个栈帧出现部分重叠,在进行方法调用时可共享一部分数据

栈帧间的数据共享

动态连接

每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有该引用是为了支持方法调用过程中的动态连接。Class文件常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数

这些符号引用一部分会在类加载阶段或者第一次使用转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接

方法返回地址

当方法开始执行后,只有两种方式可以推出该方法,方式一执行引擎遇到任意一个方法返回的字节码指令,可能有返回值传递给上层方法调用者,是否有返回值和返回值类型将根据遇到何种方法返回指令来决定。方式二方法在执行过程中遇到异常,且该异常在方法体中没得到处理

无论哪种方式退出,都需要返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。方法正常退出时,调用者的PC计数器的值可作为返回地址,栈帧中很可能会保存该PC计数器值;方法异常退出时,返回地址要通过异常处理器来确定,栈帧中一般不会保存这部分信息。

方法退出过程实际上等同于当前栈帧出栈,退出可能执行的操作有,恢复上层方法的局部变量表和操作数栈,若有返回值把返回值压入调用者栈帧的操作数栈中调整PC计数器的值以指向方法调用指令后面的一条指令等。

附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范种没有描述的信息到栈帧中,例如调试相关的信息。实际开发中一般会把动态连接、方法的返回地址、与其他附加信息全部归类为一类,称为栈帧信息。