JVM虚拟机中的栈帧
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,存在于虚拟机运行时数据区中的虚拟机栈栈中的元素。
一个栈帧主要有 局部变量表 、操作数栈 、动态连接、方法出口 。
每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
在编译程序代码的时候,栈帧中需要多大的局部变量表、多深的操作数栈已经完全确定了,并且写入到方法表的 Code 属性之中,因为一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。
局部变量表
是一组变量值储存空间,用于存放方法参数和方法内部定义的局部变量。
在 Java 程序被编译为 Class 文件时,就在方法的 Code 属性的 max_locals 数据项中确定了该方法所需要分配的最大局部变量表的容量。
虚拟机通过索引定位的方式使用局部变量表,索引值得范围是从 0 开始到局部变量表最大的 Slot 数量。32 为数据类型使用一个 Slot 。64 位数据类型使用两个连续的 Slot。
局部变量表第 0 位 索引的 Slot 默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字 “this” 来访问这个隐含的参数。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量 Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的 Slot。
局部变量表中的 Slot 是可重用的。具体可以看 《深入理解 Java 虚拟机》201 页。
操作数栈
数据结构是一个栈。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈和出栈操作。
例如,在做算数运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在 Class 文件中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。
这些符号引用一部分会在类加载或第一次使用的时候转化为直接引用,这种转化成为静态解析。另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。之所有为什么会有动态连接,是因为某些方法的调用,并不能直接在解析的时候进行确定,只能在运行的时候确定,并且执行动态连接进行替换,如 重写父类的方法。
方法返回地址(方法出口)
当一个方法被执行后,有两种方式退出这个方法,一种是正常退出,即执行完毕和遇到 return ;另一种是遇到了异常。
无论采用何种方式退出,在方法退出后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的 PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器的值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)亚茹调用者栈帧的操作数栈中,调整 PC 计数器的值以指向方法调用指令后面的一条指令等。
参考 《深入理解 Java 虚拟机》