JVM 执行引擎架构

理解执行引擎的架构,是深入理解 JIT 编译的基础。

执行方式

Java 代码有多种执行方式:

flowchart LR
    Source["Java 源码\n.java"] --> Compile["javac 编译"]
    Compile --> Bytecode["字节码\n.class"]
    
    subgraph 执行方式
        Interpret["解释执行"]
        JIT["JIT 编译"]
        AOT["AOT 编译"]
    end
    
    Bytecode --> Interpret
    Bytecode --> JIT
    Bytecode --> AOT

解释执行

解释执行逐条读取字节码指令并翻译成机器码执行:

  • 优点:启动快,无需等待编译
  • 缺点:执行速度慢,每次调用都需要解释

JIT 编译

JIT(Just-In-Time)编译器在运行时将热点字节码编译成机器码:

  • 优点:编译后的代码执行速度快
  • 缺点:需要预热时间,编译本身消耗资源

AOT 编译

AOT(Ahead-of-Time)编译在程序运行前将字节码编译成机器码:

  • 优点:无需预热,启动快
  • 缺点:无法利用运行时信息做激进优化

HotSpot VM 架构

HotSpot VM 是目前最主流的 JVM 实现:

flowchart TB
    subgraph 类加载["类加载子系统"]
        ClassLoader["ClassLoader"]
        ByteCode["字节码"]
    end
    
    subgraph 运行时数据区["运行时数据区"]
        Heap["堆内存"]
        Stack["虚拟机栈"]
        Method["方法区"]
        PC["程序计数器"]
    end
    
    subgraph 执行引擎["执行引擎"]
        Interpreter["解释器"]
        JIT["JIT 编译器"]
        GC["GC"]
    end
    
    ClassLoader --> ByteCode
    ByteCode --> Interpreter
    Interpreter --> JIT
    JIT --> GC

解释器

解释器逐条执行字节码指令:

// 解释器执行循环
public void interpret() {
    while (!terminated) {
        // 获取下一条字节码指令
        Bytecodes code = fetch();
        
        // 分发到对应的执行逻辑
        switch (code) {
            case ILOAD:
                pushLocalInt();
                break;
            case IADD:
                int b = popInt();
                int a = popInt();
                pushInt(a + b);
                break;
            // ... 更多指令
        }
    }
}

JIT 编译器

HotSpot VM 包含两个 JIT 编译器:

编译器名称特点
C1Client Compiler快速编译,优化激进程度低
C2Server Compiler深度优化,编译耗时长
flowchart LR
    ByteCode["字节码"] --> Tier0["解释执行"]
    Tier0 -->|"热点代码"| Tier1["C1 编译"]
    Tier1 -->|"更热点"| Tier2["C2 编译"]
    Tier2 -->|"去优化"| Tier1
    Tier1 -->|"去优化"| Tier0

分层编译

现代 JVM 采用分层编译策略:

层级编译器编译速度优化程度
0解释执行--
1C1 编译
2C1 + Profiling
3C2 编译
// 热点代码探测
public class HotSpotDetector {
    // 方法调用计数器
    private int methodCallCount = 0;
    private static final int COMPILE_THRESHOLD = 10000;
    
    public void execute() {
        methodCallCount++;
        
        // 超过阈值,触发编译
        if (methodCallCount >= COMPILE_THRESHOLD) {
            triggerJITCompilation();
        }
    }
}

执行引擎的协作

sequenceDiagram
    participant App as 应用
    participant Interp as 解释器
    participant C1 as C1 编译器
    participant C2 as C2 编译器
    participant CodeCache as 代码缓存
    
    App->>Interp: 调用方法
    Interp->>Interp: 解释执行
    Interp->>C1: 热点代码
    C1->>C1: 快速编译
    C1->>CodeCache: 生成机器码
    CodeCache-->>App: 执行编译后代码
    App->>C2: 超热点代码
    C2->>C2: 深度优化
    C2->>CodeCache: 生成优化代码

JIT 编译器的作用

JIT 编译器的主要作用:

  1. 提升执行速度:编译后的机器码比解释执行快 10~100 倍
  2. 激进优化:利用运行时信息进行深度优化
  3. 去优化:在优化假设不成立时回退到解释执行

代码缓存

编译后的机器码存储在代码缓存(Code Cache)中:

参数说明默认值
-XX:InitialCodeCacheSize代码缓存初始大小160KB
-XX:ReservedCodeCacheSize代码缓存最大大小48MB
-XX:CodeCacheExpansionSize扩展大小32KB

如果代码缓存满了,JIT 编译器会停止工作:

// 代码缓存耗尽时的日志
Java HotSpot(TM) 64-Bit Server VM warning: 
    code cache is full. 
    Compiler has been disabled.

性能影响

不同执行方式的性能对比:

执行方式启动时间峰值性能适用场景
纯解释最快最差短生命周期程序
分层编译中等最优长时运行服务
AOT最快中等Serverless、容器