# 五、字节码执行引擎
# 1. 概述
# 1.1 功能
- JVM 的字节码执行引擎的基本功能就是输入字节码文件,然后对字节码进行解析并处理,最后输出执行的结果。
# 1.2 实现方式
①通过编译器直接解释执行字节码 —— 读一句 -> 解释一句 -> 执行一句
② 通过即时编译器产生本地代码(编译执行)—— 先整体编译成机器码 -> 直接执行机器码
③ 上述两种结合
# 2. 栈帧
- 栈帧是用于支持 JVM 进行方法调用和方法执行的数据结构。
- 栈帧随着方法调用而创建,随着方法结束而销毁。
- 栈帧里面存储了:
- 方法的局部变量
- 操作数栈
- 动态连接
- 方法返回地址
- ....
# 2.1 局部变量表
用来存放方法参数和方法内部定义的局部变量的存储空间。
以变量槽 slot 为单位,目前一个 slot 存放 32 位以内的数据类型。
对于 64 位数据(如 long、double)占 2 个 slot。
对于实例方法,第 0 位 slot 存放的是 this,然后从 1 到 n,依次分配给参数列表和局部变量。
//实例方法 public int add(int a, int b){ int c = a + b; return a + b + c; //slot 排位 //0--this //1--a //2--b //3--c }
使用
javap -verbose
反汇编后得到的结果如下:对于静态方法,就没有 this 了
//静态方法 public static int staticAdd(int a,int b){ int c = a + b; return a + b + c; //slot 排位 //0--a //1--b //2--c }
slot 是复用的,以节省栈帧的空间,这种设计可能会影响到系统的垃圾回收行为。所以当确定引用不会再被使用的时候,最好是显式将其置为 null。
public static void main(String[] args) { //定义一个 2M 的数组 { byte[] bs = new byte[2 * 1024 * 1024]; } //出了作用域后,bs 已经没用了,但是它的插槽仍然存在:slot排位 //0--args //1--bs1 int a = 0; //加入 int a=0 后,为了节省栈帧的空间,slot 进行复用,所以 1 号 slot 就分给了 a //slot排位 //0--args //1--a //并不保证 gc 一定会触发 System.gc(); }
# 2.2 操作数栈
用来存放方法运行期间,各个指令操作的数据。
- 操作数栈中元素的数据类型必须和字节码指令的顺序严格匹配。
- 虚拟机在实现栈帧的时候可能会做一些优化,让两个栈帧出现部分重叠区域,以存放公用的数据。
# 2.3 动态链接
每个栈帧都有一个指向运行时常量池中该栈帧所属方法的引用,以支持方法调用过程的动态链接。
- 静态解析:类加载的时候,符号引用就转化为直接引用。
- 动态链接:运行期间转换为直接引用。
# 2.4 方法返回地址
方法执行后返回的地址(方法被调用的地方)。
# 2.5 方法调用
确定具体调用哪一个方法,并不涉及方法内部的执行过程(只是一个找的过程,并不会执行方法)。
- 部分方法是直接在类加载的解析阶段,就确定了引用关系。如静态方法、私有方法、实例构造器、父类方法。
- 但是对于实例方法,也称虚方法,因为重载和多态,需要运行期间动态委派。
# 2.6 分派
# 静态分派
所有依赖静态类型来定位方法执行版本的分派方式。
如:重载方法(看参数列表的数据类型)
# 动态分派
根据运行期的实际类型来定位方法执行版本的分派方式。
如:覆盖方法