C1 编译器(Client Compiler)

理解 C1 编译器,是理解 JVM 分层编译策略的基础。

C1 编译器概述

C1 编译器有以下特点:

特性说明
编译速度快,适合快速编译
优化程度低~中,基础优化
适用场景客户端应用、短期运行
代码质量比解释执行快,但不如 C2
flowchart LR
    subgraph C1编译流程
        A["字节码"] --> B["C1 编译器"]
        B --> C["机器码\n快速但简单"]
    end
    
    subgraph C2编译流程
        D["字节码"] --> E["C2 编译器"]
        E --> F["机器码\n深度优化"]
    end
    
    style B fill:#feca57
    style E fill:#ff6b6b

C1 编译器架构

C1 编译器分为多个阶段:

flowchart TB
    subgraph C1编译器阶段
        A["字节码输入"]
        B["HIR 生成\n高级中间表示"]
        C["HIR 优化"]
        D["LIR 生成\n低级中间表示"]
        E["LIR 优化"]
        F["机器码生成"]
    end
    
    A --> B --> C --> D --> E --> F

阶段一:HIR 生成

将字节码转换为高级中间表示(HIR):

// 字节码
iload_1
iload_2
iadd
istore_3

// HIR 表示
Block 0:
  t1 = LoadLocal(int, local[1])
  t2 = LoadLocal(int, local[2])
  t3 = Add(t1, t2)
  StoreLocal(local[3], t3)

阶段二:HIR 优化

在 HIR 层面进行基础优化:

  • 常量折叠
  • 代数简化
  • 公共子表达式消除

阶段三:LIR 生成

将 HIR 转换为低级中间表示(LIR):

// LIR 表示(更接近机器码)
mov eax, [ebp+8]     // 加载 local[1]
mov ebx, [ebp+12]    // 加载 local[2]
add eax, ebx          // 相加
mov [ebp+16], eax     // 存储到 local[3]

阶段四:LIR 优化

在 LIR 层面进行低层次优化:

  • 寄存器分配
  • 指令调度
  • 基本块重排序

C1 与 C2 的区别

特性C1C2
编译速度
优化程度
适用场景客户端服务端
编译阈值较低较高
代码质量较好最优
flowchart TB
    subgraph 性能曲线
        A["解释执行"]
        B["C1 编译"]
        C["C2 编译"]
        T["时间"]
        P["性能"]
    end
    
    T --> P
    P --> A
    A --> B
    B --> C
    
    style A fill:#dfe6e9
    style B fill:#feca57
    style C fill:#ff6b6b

C1 编译器参数

参数说明默认值
-XX:TieredCompilation启用分层编译JDK 8+ 默认开启
-XX:Tier3InvocationThresholdTier 3 触发阈值200
-XX:Tier3MinInvocationThresholdTier 3 最小调用阈值100
-XX:Tier3BackEdgeThresholdTier 3 回边阈值100000

C1 的优化策略

C1 编译器主要进行以下优化:

1. 基本块重排序

// 重排序前
if (condition) {
    doA();
    doB();
} else {
    doC();
}

// 重排序后(优化跳转)
// 将热路径放在前面,减少跳转

2. 窥孔优化

// 窥孔优化示例
// 优化前
i = i + 0;        // 无意义操作
x = x * 1;        // 无意义操作

// 优化后
// 删除无意义操作

3. 冗余加载消除

// 冗余加载消除
// 优化前
a = load(x);      // 第一次加载
b = a + 1;
c = load(x);      // 冗余加载
d = c + 2;

// 优化后
a = load(x);
b = a + 1;
c = a;            // 复用之前的加载结果
d = c + 2;

4. 简单内联

C1 会进行简单的内联优化:

// 内联前
public int add(int a, int b) {
    return a + b;
}

public int calculate() {
    return add(1, 2);  // 热点调用
}

// 内联后
public int calculate() {
    return 1 + 2;     // 内联展开
}

C1 的使用场景

C1 编译器适合以下场景:

  1. 短生命周期应用:命令行工具、一次性脚本
  2. 对启动时间敏感:GUI 应用、服务启动
  3. 不需要极致性能:辅助工具、调试程序

禁用 C1

在某些情况下,可能需要禁用 C1:

# 禁用 C1,只使用 C2
java -XX:TieredCompilation \
     -XX:TieredStopAtLevel=3 \
     -jar application.jar

# 禁用分层编译
java -XX:-TieredCompilation \
     -jar application.jar

性能监控

观察 C1 编译

# 开启编译日志
java -XX:+UnlockDiagnosticVMOptions \
     -XX:+LogCompilation \
     -XX:LogFile=/tmp/jit.log \
     -jar application.jar

# 观察 C1 编译的日志
grep "compiler='C1'" /tmp/jit.log

编译计数器

# 使用 jstat 查看编译统计
jstat -printcompilation <pid> 1000

C1 与分层编译

在分层编译模式下,C1 的角色:

flowchart LR
    subgraph 分层编译中的C1
        T0["Tier 0\n解释执行"]
        T1["Tier 1\nC1 快速编译"]
        T2["Tier 2\nC1 + Profiling"]
        T3["Tier 3\nC2 深度优化"]
    end
    
    T0 -->|"首次热点"| T1
    T1 -->|"继续热点"| T2
    T2 -->|"超热点"| T3
    T3 -.->|"去优化"| T2

C1 在分层编译中的价值:

  • 提供快速的首层编译
  • 采集 profiling 数据
  • 快速响应热点代码