一起学Java虚拟机(五):运行时栈帧结构

384 阅读6分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

一起学Java虚拟机系列

前言

了解JVM是对Java程序员的基本要求,但是有多少同学和我有一样醉心解bug堆布局,忘记了内功修炼,对JVM的理解是零碎的。系统地学习一次JVM也许能让我们在这条路走得更好更远。

栈帧的概念结构

Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法 调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。

每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

对于执行引擎来讲,在活动线程中,只有位于栈顶的方法才是在运行的,只有位于栈顶的栈帧才是生效的,其被称为“当前栈帧”(Current Stack Frame),与这个栈帧所关联的方法被称为“当前方法”(Current Method)。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

image.png

局部变量表

局部变量表(Local Variables Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义 的局部变量。

变量槽

局部变量表是index0开始的数组,数组的每个元素我们称为变量槽

局部变量表的容量以变量槽(Variable Slot)为最小单位,一个变量槽可以存放一个 32位以内的数据类型,Java中占用不超过32位存储空间的数据类型有boolean、byte、char、short、int、float、reference和returnAddress这8种类型。reference类型表示对一个对象实例的引用,种returnAddress类型目前已经很少见了,它是为字节码指令jsr、jsr_w和ret服务的,指向了一条字节码指令的地址。

实例方法的变量槽

实例方法(没有被static修饰的方法)局部变量表中第0位索引的变量槽默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。

变量槽的复用

为了尽可能节省栈帧耗用的内存空间,局部变量表中的变量槽是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的变量槽就可以交给其他变量来重用。

举例:

public void test1(){ 
    int a = 0;
    {
        int b = 0; 
        b = a + 1; 
    } 
    int c = 1; 
}

使用010Editor查看局部变量表

image.png 可见 b 和 c 的槽位都是index=1, 也就是说 b 的作用域结束后,c 会重复使用 b 的槽位。

操作数栈

Java虚拟机的解释执行引擎被称为“基于栈的执行引擎”,里面的“栈”就是操作数栈。 操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。

操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占 的栈容量为1,64位数据类型所占的栈容量为2。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种 字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。譬如在做算术运算的时候是通过 将运算涉及的操作数栈压入栈顶后调用运算指令来进行的,又譬如在调用其他方法的时候是通过操作 数栈来进行方法参数的传递。

动态连接

每个栈帧中包含的指向运行时常量池中该栈帧所属方法的引用

  • 指向运行时常量池的方法引用
  • 动态链接的作用就是将符号引用转换为直接引用 Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。

方法返回地址

当一个方法开始执行后,只有两种方式退出这个方法。

  • 正常退出:执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者
  • 异常退出:在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出。

无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。一般来说,方法正常退出时,主调方法的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。

附加信息

《Java虚拟机规范》允许虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试、性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现。