JVM-栈帧的内部结构

144 阅读6分钟

一、局部变量表

1、结构
  • 局部变量表也被称为局部变量数组或本地变量表。
  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内部的局部变量,这些数据类型包括各类基本数据类型、对象引用以及returnAddress类型,32位的类型占一个slot,64位的类型(long和double)占用两个slot。byte、short、char、boolean会转化成int类型
  • 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据的安全问题。
  • 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maxinum,local,variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
  • 方法嵌套的次数由栈的大小来决定。栈越大,方法嵌套的次数就越多。
  • 局部变量表的方法只在当前方法中调用有效。当方法调用结束后,方法栈帧销毁,局部变量表也会随之销毁。 屏幕截图 2024-10-23 195931.png 字节码阅读器jclasslib插件,图中起始pc是指对应字节码指令的行号, 后边的行号代表java代码的行号
2、slot(变量槽)
  • 是局部变量表的基本单位。
  • JVM会为局部变量表中的每个slot都分配一个访问索引,通过索引即可成功访问到局部变量表中指定的局部变量值。
  • 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每个slot上。
  • 如果需要访问局部变量表中的一个64位的变量值时,需使用前一个索引值。
  • 如果当前帧是由构造方法或实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数表顺序继续排列。
  • 栈帧中局部变量表的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么在之后新申请的变量可能会复用过期变量的槽位,以达到节省资源的目的。

屏幕截图 2024-10-24 191759.png

二、操作数栈(或表达式栈)

  • 每个独立的栈帧中,除了包含局部变量表之外,还包含一个后进先出的操作数栈,也可以称为表达式栈。
  • 在方法执行的过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈。操作时会先将栈中数据取出,执行后再压入栈,相关操作如执行复制、交换、求和。
  • 主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间。
  • 操作数栈是在方法一执行创建栈帧时创建的,这个时候操作数栈是空的。
  • 操作数栈的深度在编译时就已经确定,保存在方法的code属性中的max_stack。
  • 操作数栈不能采用索引的方式来访问数据,只能通过入栈和出栈操作。
  • 如果被调用的方法带有返回值的话,返回值也需压入当前栈帧的操作数栈中。

三、动态链接(指向运行时常量池的方法引用)

每个栈帧内部都包括一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中。比如,描述一个方法调用另外其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转化为调用方法的直接引用。 常量池的作用:为了提供一些符号和常量,便于指令的识别。

四、方法的调用

  • 静态链接

    当一个字节码文件被装载到虚拟机的时候,如果被调用的目标方法在编译器可知,且运行时保持不变。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

  • 动态链接

    如果被调用的方法在编译器无法确定下来,只能够在程序运行时将调用方法的符号引用转换为直接引用,这种情况称之为动态链接。

  • 早期绑定

    绑定是指一个字段、方法或者类在符号引用被替换为直接引用的过程,这个仅仅发生一次。如果目标方法在编译期可知,且运行时保持不变,就可以将这个方法与所属类型进行绑定,就可以使用静态链接的方式将符号引用转化为直接引用。如:类中的构造方法。

  • 晚期绑定

    如果目标方法在编译期无法确定,只能在运行期将这个方法与所属类型进行绑定,这种绑定方式称之为晚期绑定。如:接口中的方法。

  • 非虚方法

    如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。如:静态方法、私有方法、final方法、实例构造器、父类方法。

  • 调用指令

  1. invokestatic:调用静态方法
  2. invokespecial:调用方法、私有以及父类方法
  3. invokevirtual:调用所有虚方法
  4. invokeinterface:调用接口方法
  5. invokedynamic:动态解析出需要调用的方法、然后执行

五、方法返回地址(或方法正常退出或异常退出的定义)

  1. 存放调用该方法的pc寄存器的值
  2. 无论通过哪种方式退出,在方法退出后都返回到该方法调用的位置。正常退出时,调用者的pc计数器的值作为方法返回地址,即调用该方法的指令的下一条指令的地址。异常退出的返回地址要通过异常表来确定。
  3. 执行引擎遇到任意一个方法返回的字节码指令,会有返回值传递给上层的方法调用者,简称正常完成出口。一个方法在正常调用完成之后究竟需要使用哪一个返回指令还需要根据方法返回值的实际数据类型而定。在字节码指令中,返回指令包含ireturn(当返回值是boolean、byte、char、short、int类型时使用),lreturn、freturn、dreturn以及areturn,另外还有一个return指令供声明void的方法、实例初始化方法、类和接口的初始化方法使用。
  4. 方法执行过程中抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码。