java JVM运行时栈帧结构

来源:互联网 发布:linux snmptrap接收 编辑:程序博客网 时间:2024/06/07 19:55

JVM中数据区域分为虚拟机栈、JAVA堆、方法区、运行时常量池、程序计数器(PC寄存器)这几类。在这五类中,虚拟机栈用来表示各个线程中方法执行情况的区域,而栈帧是虚拟机栈中的栈元素。栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。每一个方法从调用开始到执行完成的过程都可以看作是一个栈帧于虚拟机栈中从入栈到出栈的过程。

这里写图片描述

一个线程中有很多个栈帧,只有位于这个栈最上方的栈帧才是有效的,最上方栈顶的栈帧我们称之为当前栈帧,当前栈帧关联的方法我们称之为当前方法。虚拟机的执行引擎的字节码指令只会对当前栈帧(当前方法)起作用。

栈帧组成

  1. 局部变量表
  2. 操作数栈
  3. 动态链接
  4. 返回地址
  5. 其他信息

局部变量表

局部变量表顾名思义,用于存储方法中的局部变量。值得注意的是,它不仅仅存储在方法内部声明的局部变量方法参数列表中的变量非static方法隐含的this指针try-catch块中catch中的异常对象变量都是局部变量表中的变量。局部变量表的大小在运行之前已经确定,原因是在java文件编译后的class文件的方法表的code属性中有一条属性用来表示该方法所需的局部变量表大小。

局部变量表的容量以变量槽(Slot)为单位,虚拟机规范中没有明确的规定一个变量槽具体有多大,只是很有指向性的表示除了long和double之外的数据都用一个Slot大小来存放,而long与double要用两个Slot来存放。而且需要注意的是局部变量表的大小不是简单的将每个变量的大小相加而得到的,而是通过计算每个变量的作用域,科学的计算一个最小的大小。比如一个在某段之后失效的局部变量,留下的空间可以给其他的变量做空间而不需要申请更多的局部变量表的空间。

操作数栈

操作数栈也称为操作栈,结构为一个后进先出的栈,用来进行方法中的一些运算。和局部变量表类似的,在java文件编译后的class文件中就有规定操作数栈的最大深度。很多书中都用iadd来举例,这是一个简单的例子,iadd字节码指令表示将两个int类型的数据相加,那么在这个字节码执行之前,我们肯定已经通过某些字节码将两个局部变量表中的int类型数据压入操作数栈中,然后通过iadd相加进行操作。需要注意的是在操作数栈中,只有long和double可以占有两个深度。

实际上,操作数栈非常重要,JAVA虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。而且通过某些优化手法,两个栈帧的一个操作数栈和另一个局部变量表是有重叠的,这样在方法调用的时候就可以省略某些额外的参数传递开销。

动态链接

每个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,以便对当前方法的代码实现动态链接。在class文件里面,一个方法如果要调用另外一个方法,或者访问其成员变量,则需要通过符号引用来表示,那么动态链接的作用就是在恰当的时候将这些以符号引用所表示的方法或是变量解析成直接引用。

返回地址

我们说栈帧中包括返回地址,可能这样说有些勉强,毕竟返回地址是一个返回的状态和概念,不是一个具体的结构。当一个方法开始执行后,只有两种方法能够退出这个方法。第一种方法就是在方法执行时遇到了return语句(遇到一个返回字节码指令),这个时候正常退出该方法,并且将相应的方法的返回值传递给该方法的调用者。第二种方法就是在该方法中遇到了一个异常,而且异常没有通过try-catch来捕获(在这里讨论虚拟机内部结构的时候我说try-catch这种java语法上的结构不是很好,但是为了能够方便的表达清楚我这样做了)或者是自己throw抛出一个异常(athrow指令抛出异常)。在这两种情况下,只要在本方法中没有匹配到异常处理器,就会导致方法退出,一个异常退出的方法不会再给该方法的调用者一个返回值。

正常的退出情况,退回到该方法的调用者中被调用的位置(这个具体的位置被PC寄存器记录)。而异常的退出情况返回地址要通过异常处理器来确定,栈帧中一般不保存这部分信息。

其他信息

虚拟机规范中允许具体的虚拟机在实现的时候增加一些规范中没有的信息。在实际开发的过程中,一般我们会把动态链接方法返回地址与其他附加信息归为一类。