运行时栈帧结构

介绍

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

  • 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。

    • 一个栈帧需要分配多少内存, 并不会受到程序运行期变量数据的影响, 而仅仅取决于程序源码和具体虚拟机实现的栈内存布局形式。
  • 每一个方法从调用开始至执行结束的过程, 都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

  • 一个线程中的调用链可能会很长,
    • 对于Java程序来说, 同一时刻、同一条线程里面, 在调用堆栈的所有方法都同时处于执行状态。
    • 对于执行引擎来说, 在活动线程中, 只有位于栈顶的方法才是在运行的, 只有位于栈顶的栈帧才是生效的, 其被称为 "当前栈帧"(Current Stack Frame), 与这个栈帧所关联的方法被称为"当前方法"。
  • 栈帧的概念结构图

    运行时栈帧结构_第1张图片

局部变量表(Local Variables Table)

  • 是一组变量值的存储空间, 用于存放方法参数与方法内部定义的局部变量。在Java程序被编译为Class文件时, 就在方法的Code属性的max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量。

  • 局部变量表的容量以变量槽(Variable Slot)为最小单位。

  • Java虚拟机规范并没有明确指出一个变量槽应占用的内存大小, 只是很有导向性地说到每个变量槽应该能够存放一个boolean、byte、char、short、int、float、reference 或 returnAddress 类型的数据。

  • 允许变量槽的长度可以随着处理器, 操作系统或虚拟机的实现的不同而发生变化, 保证了即使在64位虚拟机中使用了64位的物理物理内存空间去实现一个变量槽, 虚拟机仍要使用对其和补白的手段让变量槽在外观上看起来与32位虚拟机中的一致。

  • Java 虚拟机数据类型: boolean、byte、char、short、int、float、reference、returnAddress。

    • reference 类型表示对一个对象实例的引用(强软弱虚)
      • 虚拟机能根据引用直接或间接地查找对象在Java堆中的数据存放的起始地址或索引
      • 虚拟机能根据引用直接或间接地查找到对象所属数据类型的方法区中的存储的类型信息
    • returnAddress 是为字节码指令 jsr(Java Specification Requests), jsr_w 和 ret服务的, 指向了一条字节码指令的地址。
  • Java虚拟机通过索引定位的方式使用局部变量表, 索引值的范围是从0开始至局部变量最大的变量槽数量。

    • 如果访问的是32位数据类型的变量, 索引N就代表了使用第N个变量槽。
    • 如果访问的是64位数据类型的变量, 则说明会同时使用第N和N+1两个变量槽。
  • 为了尽可能地节省栈帧耗用的内存空间, 局部变量表中的变量槽是可以重用的, 方法体中定义的变量, 其作用域并不一定会覆盖整个方法体。

    • 如果当前字节码PC计数器的值已经超出了某个变量的作用域, 那这个变量对应的变量槽就可以交给其他变量来用。
    • 不过这样会伴随有少量额外的副作用, 例如在某些情况下变量槽的复用会直接影响到系统的GC行为。

操作数栈(Operand Stack)

  • 一个LIFO的栈, 同局部变量表一样, 操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks 数据项之中。

  • 操作数栈的每一个元素都可以是包括 long 和 double 在内的任意 Java 数据类型。

  • 32位数据类型所占的栈容量为1, 64位数据类型所占的栈容量为2。

  • Javac 编译器的数据流分析工作保证了在方法执行的任何时候, 操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。

  • 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。

  • 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配。

  • 两个不同栈帧作为不同方法的虚拟机栈的元素, 在概念上是完全独立的。但是在大多数虚拟机的实现中都会进行一些优化处理, 另两个栈帧出现一部分重叠, 从而节约一些空间, 并且在方法调用时就可以直接共用一部分数据, 无需进行而外的参数复制传递

  • 两个栈帧之间的数据共享示意图:

    运行时栈帧结构_第2张图片

动态连接

  • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
  • Class文件的常量池中存有的大量符号引用中的一部分会在类加载阶段或者第一次使用的时候就被转换为直接引用, 该转化即为动态连接。

方法返回地址

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

    • 第一种方式使执行引擎遇到任意一个方法返回的字节码指令(正常调用完成 [Normal Method Invocation Completion])。
    • 第二种是在方法执行的过程中遇到了异常, 并且此异常没有在方法体内得到妥善处理(异常调用完成 [Abrupt Method Invocation Completion])。

你可能感兴趣的:(运行时栈帧结构)