字节码与解释执行

理解字节码,是深入理解 JIT 编译器工作的基础。

Class 文件结构

一个 Class 文件是一个二进制流,由以下部分组成:

flowchart TB
    subgraph Class文件结构
        Magic["Magic (0xCAFEBABE)\n4 字节"]
        Version["Version\nminor_version + major_version"]
        ConstantPool["Constant Pool\n常量池"]
        AccessFlags["Access Flags\n访问标志"]
        ThisClass["This Class\n当前类"]
        SuperClass["Super Class\n父类"]
        Interfaces["Interfaces\n接口表"]
        Fields["Fields\n字段表"]
        Methods["Methods\n方法表"]
        Attributes["Attributes\n属性表"]
    end

重要字段说明

字段说明
Magic固定为 0xCAFEBABE,用于识别 Class 文件
VersionClass 文件版本号,如 52.0 表示 Java 8
Constant Pool存储所有字面量和符号引用
Access Flagspublic、final、super 等标志

字节码指令集

字节码指令分为以下几类:

类型示例指令说明
加载/存储iload, istore在局部变量和操作数栈之间移动数据
运算iadd, isub整数算术运算
类型转换i2l, i2d整数转长整数、浮点数
控制转移ifeq, goto条件跳转和无条件跳转
方法调用invokevirtual, invokespecial调用方法
返回ireturn, areturn方法返回

使用 javap 查看字节码

基本用法

# 查看类的字节码
javap -c MyClass.class

# 查看详细信息
javap -verbose MyClass.class

字节码示例

// Java 源码
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}
// 使用 javap 查看字节码
javap -c Calculator.class

// 输出
Compiled from "Calculator.java"
public class Calculator {
  public Calculator();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int add(int, int);
    descriptor: (II)I
    Code:
       0: iload_1           // 将局部变量 1(参数 a)压入栈
       1: iload_2           // 将局部变量 2(参数 b)压入栈
       2: iadd              // 弹出栈顶两元素,相加后压回栈
       3: ireturn           // 返回结果
}

操作数栈与局部变量表

操作数栈(Operand Stack)

操作数栈是后进先出(LIFO)的栈,用于字节码执行时的临时存储:

// 源码
int a = 1;
int b = 2;
int c = a + b;
// 对应字节码
iconst_1       // 将常量 1 压入栈
istore_1       // 弹出栈顶,存入局部变量 1(a)
iconst_2       // 将常量 2 压入栈
istore_2       // 弹出栈顶,存入局部变量 2(b)
iload_1        // 将 a 压入栈
iload_2        // 将 b 压入栈
iadd           // 弹出两元素,相加后压回栈
istore_3       // 弹出栈顶,存入局部变量 3(c)

局部变量表(Local Variable Table)

局部变量表存储方法参数和局部变量:

// 方法的局部变量表
public int calculate(int a, int b) {  // a = local 1, b = local 2
    int c = a + b;                     // c = local 3
    return c;                           // 使用 local 3
}
// local 0 是 this 引用

常见字节码指令详解

方法调用指令

指令说明使用场景
invokevirtual调用虚方法普通方法调用
invokespecial调用构造器、私有方法、父类方法构造方法、private、方法
invokestatic调用静态方法静态方法调用
invokeinterface调用接口方法接口方法调用
// invokevirtual 示例
public class Example {
    public void sayHello() {
        System.out.println("Hello");
    }
}

// 对应字节码
public void sayHello();
  0: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
  3: ldc           #3    // String Hello
  5: invokevirtual #4    // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  8: return

控制转移指令

// if-else 的字节码
public int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

// 对应字节码
public int max(int, int);
  0: iload_1           // 加载 a
  1: iload_2           // 加载 b
  2: if_icmple 7       // 如果 a <= b,跳转到 7
  3: iload_1           // 加载 a
  4: istore_3          // 存入局部变量
  5: iload_3           // 加载返回值
  6: ireturn           // 返回
  7: iload_2           // 加载 b
  8: ireturn           // 返回

循环的字节码

// for 循环
public int sum(int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += i;
    }
    return sum;
}

// 对应字节码
public int sum(int);
  0: iconst_0          // sum = 0
  1: istore_2
  2: iconst_0          // i = 0
  3: istore_3
  4: iload_3            // if i < n
  5: iload_1
  6: if_icmpge 16      // if i >= n, goto 16
  7: iload_2            // sum += i
  8: iload_3
  9: iadd
 10: istore_2
 11: iinc 3, 1          // i++
 14: goto 4             // 继续循环
 16: iload_2            // return sum
 17: ireturn

同步(synchronized)的字节码

// synchronized 方法
public synchronized void doSomething() {
    // 同步代码
}

// 对应字节码
public synchronized void doSomething();
  0: ...                // 方法体
  5: return

// synchronized 代码块
public void doSomething() {
    synchronized (this) {
        // 同步代码
    }
}

// 对应字节码
public void doSomething();
  0: aload_0
  1: dup
  2: astore_1
  3: monitorenter       // 进入监视器
  4: ...                // 方法体
  7: aload_1
  8: monitorexit        // 退出监视器
  9: goto 17
 12: astore_2
 13: aload_1
 14: monitorexit
 15: aload_2
 16: athrow
 17: return

解释执行的过程

flowchart TD
    A["读取字节码指令"] --> B{"指令类型?"}
    B -->|加载/存储| C["操作数栈/局部变量表"]
    B -->|运算| D["执行运算"]
    B -->|控制转移| E["更新程序计数器"]
    B -->|方法调用| F["栈帧操作"]
    
    C --> G["取下一条指令"]
    D --> G
    E --> G
    F --> G
    
    G -->|循环| A

解释执行的特点:

  1. 逐条执行:每条指令都需要解释
  2. 无优化:不进行任何优化
  3. 启动快:无需等待编译
  4. 运行慢:重复执行相同代码也需要重复解释