02 字节码原理初步 —— 基于栈的执行引擎
一、栈、寄存器
虚拟机常用实现方式:Stack based(基于栈)和 Register based(基于寄存器)
Stack based
- HotSpot JVM
- .net CLR
Register based
- LuaVM
- DalvikVM
基于栈的指令集移植性更好,代码更紧凑、编译器实现更简单
基于寄存器的指令集完成相同功能所需的指令数更少,执行速度更快一些
二、栈帧(Stack Frame)
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构
- 栈帧随着方法调用而创建,随着方法结束而销毁
- 栈帧存储空间分配在 Java 虚拟机栈中
- 每个栈帧都有自己的局部变量表(Local Variables)、操作数栈(Operand Stack)和指向运行时常量池的引用
局部变量表
- 每个栈帧内部都包含一组称为局部变量表的变量列表
- 局部变量表的大小在编译期间就已经确定
- JVM 使用局部变量表来完成方法调用时的参数传递
当一个方法被调用时,方法对应的参数会被传递到局部变量表中,下标从 0 开始。当实例方法是非静态方法,下标为 0 的局部变量就是 this(实例方法的对象的引用)
操作数栈
- 每个栈帧内部都包含一个称为操作数栈的栈
- 栈的大小在编译期间确定
在方法调用时,用来准备调用方法的参数和接收方法返回的结果
三、举个 xue 微复杂的栗子
public class ScoreCalculator {
public void record(double score) {
}
public double getAverage() {
return 0;
}
public static void main(String[] args) {
ScoreCalculator calculator = new ScoreCalculator();
int score1 = 1;
int score2 = 2;
calculator.record(score1);
calculator.record(score2);
double avg = calculator.getAverage();
}
}
javap 查看字节码
javap -c -v ScoreCalculator
复制 main() 函数部分,如下
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=6, args_size=1
0: new #2 // class ScoreCalculator
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: iconst_1
9: istore_2
10: iconst_2
11: istore_3
12: aload_1
13: iload_2
14: i2d
15: invokevirtual #4 // Method record:(D)V
18: aload_1
19: iload_3
20: i2d
21: invokevirtual #4 // Method record:(D)V
24: aload_1
25: invokevirtual #5 // Method getAverage:()D
28: dstore 4
30: return
- 6 ~ 9 行:新建了一个 ScoreCalculator 对象,使用 astore_1 存储在局部变量表 calculator 中,即把操作数栈栈顶的值(calculator)存储到局部变量表下标为 1 的位置上(dup 是啥,后面讲)
- 10 ~ 13 行:iconst_1 和 iconst_2 用来将整数 1 和 2 加载到栈顶,istore_2 后 istore_3 用来将栈顶的元素存储到局部变量表下标为 2 和 3 的位置上
- 14 ~ 17 行:对应 calculator.record(score1) 这行代码的执行,aload_1 从局部变量表下标为 1 的位置加载变量 calculator,iload_2 从局部变量表下标为 2 的位置加载变量 score1,i2d 将 int 类型的 score1 转为 double 类型的值后重新入栈,到此参数全部就绪,然后通过 invokevirtual 执行方法调用
- 18 ~ 21 行:同上
- 22 ~ 24 行:对应 double avg = calculator.getAverage() 这行代码的执行,aload_1 从局部变量表下标为 1 的位置加载变量 calculator,没有参数,到此参数全部就绪,然后通过 invokevirtual 执行方法调用,再使用 dstore 4 将存储方法返回值 avg 到局部变量表下标为 4 的位置
注意上面的第 5 行,为什么 locals 为 6 呢?
stack=3, locals=6, args_size=1
avg 是 double 类型变量,需要两个槽位 slot