栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
栈帧的概念结构
局部变量表
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java代码编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了概方法所需分配的局部变量表的最大容量。
局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小,只是很有导向性地说到每个Slot都应该存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这8种数据类型都可以使用32位或更小的物理内存来存放。
操作数栈
操作数栈(Operand Stack)也常称操作栈,它是一个后入先出(Last in First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。字节码中的方法调用指令以常量池中指向方法的符号引用作为参数,这些符号引用一部分在类加载阶段或第一次使用的时候就转化为直接引用,这种转化为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接
方法返回地址
当一个方法开始执行后,只有两种方式可以退出该方法。一种是执行引擎遇到任意一个方法返回的字节码指令,这时可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来确定,这种退出方式称为正常完成出口(Normal Method Invocation Completion)。
另一种退出方式是,在方法执行过程中遇到异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配异常处理器,就会导致方法退出,这种退出方法的方式称为异常出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。
无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。方法退出的过程实际上等同于当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向调用指令后面的一条指令等。
附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。
参考资料
- 深入理解Java虚拟机 JVM高级特性与最佳实践 第2版
网友评论