热点代码探测(C1/C2/Graal/AOT)
什么是热点代码
热点代码(Hot Spot)是 JVM 对「被频繁执行的代码」的称呼。JIT 编译器只编译热点代码,而不是全部代码——这是 JIT 的核心策略。
为什么要区分?因为编译有开销,如果对所有代码都进行 JIT 编译:
- 编译本身耗时,程序启动变慢
- Code Cache 空间有限,全部编译装不下
- 大多数代码只执行一次或几次,编译投入不划算
热点探测的方法
1. 方法调用计数器(Method Invocation Counter)
每个方法有一个计数器,记录被调用的次数。超过阈值就触发 JIT 编译。
java
public class MethodCounter {
public static void main(String[] args) {
// 这个方法被调用一次,计数器 +1
hotMethod();
}
static void hotMethod() {
// 如果被调用超过阈值(如 10000 次)
// → 触发 JIT 编译
}
}HotSpot 的方法调用计数器阈值:
| 编译器 | 默认阈值 | 说明 |
|---|---|---|
| C1(Client) | 1,500 | 快速编译,允许较高的阈值容忍 |
| C2(Server) | 10,000 | 激进优化,需要更多调用数据 |
2. 回边计数器(Backedge Counter)
回边计数器记录循环体的执行次数。循环的「回边」是从循环体末尾回到开头的指令。
java
public class BackedgeCounter {
public static void main(String[] args) {
int sum = 0;
// 这个循环体被执行 1000000 次
// 回边计数器不断累加
// 超过阈值时 → 触发 JIT 编译
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
}
}3. 方法调用计数器 + 回边计数器
HotSpot 同时使用两种计数器,更全面地探测热点:
热点探测机制:
│
├── 方法调用计数器(Method Counter)
│ 记录:invokevirtual / invokestatic / invokespecial
│ 阈值:C1 = 1500, C2 = 10000
│
└── 回边计数器(Backedge Counter)
记录:for/while/do 循环的回边
阈值:同样基于方法调用阈值计算分层编译与热点层级
分层编译把 JIT 编译分为多个层级,每个层级有不同的热点探测策略:
Tier 0:纯解释执行
- 不做任何编译
- 收集方法调用计数器和回边计数器
- 触发条件:方法开始执行时立即进入
Tier 1:C1 快速编译
- 快速编译,不做激进优化
- 启用基础的 profiling(调用计数、分支信息)
- 触发条件:方法调用计数 > 1500(C1 阈值)
Tier 2/3:C1 完整编译
- 更详细的 profiling(类型信息、分支预测)
- 编译速度稍慢,但收集更多优化信息
- 触发条件:Tier 1 持续热点
Tier 4:C2 完全编译
- 最激进的优化
- 基于 Tier 2/3 收集的 profile 信息
- 触发条件:调用计数 > 10000(C2 阈值)
调用次数
│
10k ─┼──────────────────────────────────── C2 编译(峰值性能)
│ ╭──────────────────────────────────╮
1.5k ─┼──── C1 编译 ─→ C1 完整编译 ─→ C2 编译 ─╯
│ ↑
│ 解释执行
│
└──+──────→ 时间/执行次数C1 vs C2:两种编译器的人格
HotSpot 有两个 JIT 编译器,它们有完全不同的设计目标:
C1(Client Compiler)
C1 是客户端编译器,目标是快速编译。
bash
# 启用 C1
java -client MyApp
# 只用 C1(关闭 C2)
java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 MyApp特点:
- 编译速度快,但优化程度低
- 适合启动阶段或短期程序
- 在分层编译模式下作为第一层编译
C2(Server Compiler)
C2 是服务端编译器,目标是峰值性能。
bash
# 启用 C2
java -server MyApp
# 只用 C2(跳过 C1)
java -XX:+TieredCompilation -XX:TieredStopAtLevel=4 MyApp特点:
- 编译速度慢,但优化程度高
- 内联、去虚化、逃逸分析等激进优化
- 适合长期运行的服务端应用
C1 和 C2 的核心区别
| 维度 | C1(Client) | C2(Server) |
|---|---|---|
| 设计目标 | 快速编译 | 峰值性能 |
| 编译时间 | 短 | 长 |
| 优化程度 | 轻量级 | 激进 |
| 适用场景 | 短期程序、客户端 | 长期服务 |
| 内联策略 | 保守 | 激进(更大的内联阈值) |
| 逃逸分析 | 有限 | 完整 |
Graal:新一代编译器
从 JDK 9 开始,HotSpot 可以使用 Graal 编译器替代 C2(通过 -XX:+UseGraalCompiler,JDK 10+)。
Graal 的特点
| 维度 | C2 | Graal |
|---|---|---|
| 语言 | C++ | Java(AOT 编译) |
| 优化能力 | 强 | 强(某些场景更强) |
| 编译速度 | 慢 | 更快(某些场景) |
| JVM 集成 | 内嵌 HotSpot | 可插拔 |
| Truffle 框架 | 不支持 | 支持(语言实现框架) |
GraalVM
GraalVM 是基于 Graal 编译器构建的通用运行时:
GraalVM 架构:
┌──────────────────────────────────────┐
│ 上层应用 │
│ Java / JavaScript / Python / Ruby │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ GraalVM Compiler │
│ (用 Java 实现的 JIT/AOT 编译器) │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ LLVM / JVMCI / Substrate VM │
└──────────────────────────────────────┘GraalVM 可以:
- 高性能执行:用 Graal JIT 替换 C2
- 多语言支持:在同一个 JVM 上运行 Python、JS、R 等
- Native Image:AOT 编译成独立可执行文件
AOT:编译的极致提前
AOT(Ahead-Of-Time)编译在程序运行之前完成编译,彻底消灭 JIT 阶段。
bash
# JDK 9+ 使用 jaotc 预编译
jaotc --output libHelloWorld.so HelloWorld.class
# 运行使用 AOT 编译的代码
java -XX:AOTLibrary=./libHelloWorld.so HelloWorldAOT vs JIT
| 维度 | JIT 编译 | AOT 编译 |
|---|---|---|
| 编译时机 | 运行时 | 运行前 |
| 运行时信息 | ✅ 可用(profile-guided) | ❌ 不可用 |
| 启动速度 | 慢(需要预热) | 快(直接运行机器码) |
| 峰值性能 | 高(JIT 优化) | 较低 |
| 适用场景 | 服务器长期运行 | 短期程序、容器 |
热点探测参数调优
bash
# 查看热点探测阈值
java -XX:+PrintFlagsFinal -version | grep -i threshold
# 调整方法调用计数阈值
java -XX:CompileThreshold=10000 MyApp
# 调整分层编译停层级
java -XX:TieredStopAtLevel=1 MyApp # 只用 C1
java -XX:TieredStopAtLevel=4 MyApp # 用 C1 + C2
# 调整 C1/C2 的切换阈值
java -XX:TieredCompilation \
-XX:Tier3LoadFeedback=2000 \
-XX:Tier2CompileThreshold=2000 \
MyApp
# 关闭分层编译(使用纯 C2)
java -Xcomp MyApp本节小结
热点代码探测的核心要点:
| 探测方式 | 说明 |
|---|---|
| 方法调用计数器 | 记录方法被调用次数,超过阈值触发编译 |
| 回边计数器 | 记录循环体执行次数 |
| 分层编译 | Tier 0~4,从解释到 C1 到 C2 |
| C1 vs C2 | C1 快速编译,C2 峰值优化 |
| Graal | Java 实现的 JIT/AOT 编译器 |
| AOT | 程序运行前完成编译,无 JIT 阶段 |
理解热点探测和分层编译,是理解 JVM 性能调优的关键。
到这里,「执行引擎」专题全部完成。接下来进入 String 专题(JVM 视角),首先看 String 不可变性/底层结构/内存分配。
