JVM系列之栈帧(Stack Frame)结构

栈帧的概念和结构

栈帧是运行时数据区中虚拟机栈的栈元素,用于支持虚拟机进行方法调用和执行的数据结构,栈帧中储存着局部变量表操作栈动态链接返回地址以及一些额外的附加信息。每个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

在编译的时候,栈帧中需要多大的局部变量表以及多深的操作数栈都已经完全确定了,并且存在Class类文件方法表的Code属性中(具体参看JVM系列之Class类文件的结构)。

一个线程中方法的调用链可能会很长,很多方法都同时处于执行状态,但对于执行引擎(具体参看JVM系列之字节码解释执行引擎)来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。

局部变量表(Local Variable Table)

局部变量表是一组变量值存储空间,用于存放方法参数方法内定义局部变量

在编译成Class文件时,该方法需要分配的局部变量表的最大容量就确定了,并存在方法的Code属性的max_locals项。

局部变量表的最小单元为变量槽(Variable Slot),一个Slot可以存放一个32位以内的数据类型,包括:booleanbytecharshortintfloatreference,其中reference类型表示对一个对象实例的引用(该引用中可以做到两点:从引用中可以直接或间接查找到对象在Java堆中的数据存放的起始地址索引,从引用中可以直接或间接查找到对象在所属数据类型在方法区中存储的类信息)。对于64位的数据类型(longdouble),虚拟机会以高位对齐的方式为其分配两个连续的Slot空间(与long和double类型的非原子性协定(具体参看内存模型中的long和double的非原子性协定)类似),但是局部变量表是线程私有的,所以不会存在线程安全问题。

虚拟机通过索引定位的方法来使用局部变量表,索引从0开始,如果是实例方法(非static方法),索引0一般为this的引用,而后是方法参数(按参数表顺序),再后是方法内的局部变量(按变量顺序和作用域)。操作32位数据的变量时,取一个索引值的Slot即可,当操作64位数据的变量时,则需要同时操作两个相邻的共同存放一个64位数据的Slot,单独操作其中一个是不允许的,在类加载的验证阶段会进行此校验

为了尽可能节省战阵的空间,局部变量表中的Slot是可以重用的,方法中定义的变量其作用域并不一定覆盖整个方法体,如果程序计数器的值超出了某变量的作用域了,该变量对应的Slot就可以交给其他变量使用。

操作数栈(Operand Stack)

操作数栈是一个后进先出(LIFO)栈,操作数栈的元素可以是任意的java数据类型,32位数据类型在栈中所占容量为1,64位为2。方法开始执行的时候该方法的操作数栈是空的,方法执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,即为入栈和出栈。例如:方法中做算数运算的时候是通过操作数栈来进行的(具体参看JVM系列之字节码解释执行引擎),在调用其他方法的时候是通过操作数栈来进行参数传递的。

在编译成Class文件时,该方法的操作数栈的最大深度就确定了,并存在方法的Code属性的max_stacks项。

理论上两个栈帧作为虚拟机的元素,是完全相互独立存在的。但在大多数虚拟机中会做一些优化,让两个栈帧一部分重叠。让上一个栈帧的部分操作数栈和下一个栈帧的部分局部变量表重叠,这样在进行方法调用时就可以共用一部分数据,无需进行额外的参数复制传递。

动态链接(Dynamic Linking)

所谓链接即为将Class文件中常量池的符号引用转为直接引用,这个转化过程分为两种:静态链接(在类加载阶段进行)、动态链接(在方法运行期间进行)。在编译期无法确定的被调用目标方法和成员变量(比如子类的重写方法)的直接引用,这就需要在运行期间动态链接。每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,此引用就是为了支持方法调用过程中的动态链接。

返回地址(Return Address)

当一个方法开始执行后,有两种方式退出,正常的方法返回处理(Normal Method Invocation Completion)和异常的方法返回处理(Abrupt Method Invocation Completion)。

正常的方法返回处理:当执行引擎遇到任意一个方法返回的字节码指令,可能会有返回值(是否有返回值以及返回值的类型将根据遇到何种方法返回指令决定)传递给上层的方法调用者(调用当前方法的方法称为调用者)。

异常的方法返回处理:在方法执行过程中遇到异常,并且这个异常没有在方法体内得到处理,无论jvm内部产生的异常还是代码中athrow指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法的退出。

无论何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。退出时执行的操作有:恢复上层方法的局部变量表和操作数栈,有返回值时把返回值压入调用者的操作数栈中,调整程序计数器的值指向方法调用指令后的指令位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值