9 虚拟机字节码执行引擎_栈帧结构
1 初谈执行引擎
一、关于Java虚拟机和计算机系统层面的物理机
- “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力
- 物理机的执行引擎是直接建立在处理器、缓存、指令集(例如x86-64)和操作系统面上
- 虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集
带着疑问:java虚拟机执行引擎,执行了字节码指令后,如何变成物理机能够识别并执行的指令?
二、关于Java虚拟机字节码执行引擎的概念模型
- 输入:字节码二进制流
- 处理过程:字节码解析执行的过程
- 输出:执行结果
2 运行时栈帧结构
栈帧的概述
- 方法:java虚拟机的基本执行单元
- 栈帧:一种数据结构,用于支持虚拟机进行方法调用和方法执行
- 栈帧存储了方法的局部变量表、操作数栈、动态连接、方法返回地址,其他附加信息
- 一个方法从调用开始至执行结束的过程,对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程
- 栈帧所需的内存空间在编译时确定,并不会受到程序运行期变量数据的影响
栈帧的概念结构:
2.1 局部变量表
- 局部变量表存储了两种数据:方法的参数、方法内的局部变量。
- 它的单位是变量槽(Slot)
- 长度不超过32位的数据类型,存储占用1个变量槽(byte、char、float、int、short、boolean、reference、 returnAddress等),64位的数据类型存储占用2个变量槽(double和long)。
存储结构:
说明:
- 虚拟机通过索引定位的方式使用局部变量表
- 索引值的范围是从0开始至局部变量表最大的变量槽数量。
- x表示64位类型的变量,它的索引编号为N,访问时同时使用N、N+1两个槽位
关于局部表相关的两个补充:
- 赋null值的操作在经过即时编译优化后几乎是一定会被当作无效操作消除掉的(即没有意义)
- 局部变量定义了但没有赋初始值是非法的(编译不通过、或者运行期间也会被检查出来)
上一章知识回顾:
类的量有两次赋初始值的过程,一次在准备阶段,赋系统初值;另外一次在初始化阶段,赋予程序定义的初始值。因此即使在初始化阶段没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值
问题:局部变量表数据什么时候会被回收或者覆盖?
- 局部变量表中的变量槽是可以重用的
- 处于方法执行中:如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的变量槽可以交给其他变量来重用
- 方法执行完成后:局部变量表会清空
2.2 操作数栈
- 操作数栈(Operand Stack):是一个后入先出栈(LIFO)
- 操作数栈的最大深度保存在Code属性的max_stacks数据项中
- 32位数据类型所占的栈容量为1 (64位为2)
入栈和出栈过程:(以iadd指令为例)
2.3 动态连接
- 栈帧还保存一个指向常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
- 字节码中的方法调用指令以常量池里指向方法的符号引用作为参数
- 常量池中与方法调用相关的符号引用:
- 静态解析:一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用
- 动态连接:另外一部分将在每一次运行期间都转化为直接引用
静态解析和动态连接后续介绍
2.4 方法返回地址
- 方法退出只有两种情况:执行方法返回指令、遇到异常
- 方法正常退出时,PC计数器作为返回地址,栈帧中一般会保存PC
- 方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。
方法的退出过程:
- 恢复调用方法的局部变量表和操作数栈
- 把返回值(如果有的话)压入调用者栈帧的操作数栈中
- 更新PC计数器
2.5 附加信息
《Java虚拟机规范》允许虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试、 性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现。