JIT(即时编译)
解释执行 vs 编译执行
| 解释执行 | JIT 编译 | |
|---|---|---|
| 方式 | 逐行翻译字节码执行 | 将热点字节码编译为机器码 |
| 启动速度 | 快 | 慢(需要编译时间) |
| 运行速度 | 慢 | 快(直接执行机器码) |
| 内存占用 | 低 | 较高(缓存编译后的机器码) |
JVM 默认混合模式(Mixed Mode):启动阶段解释执行,热点代码触发 JIT 编译。
热点探测
JVM 通过计数器判断哪些代码是热点:
| 计数器 | 触发对象 | 阈值(默认) |
|---|---|---|
| 方法调用计数器 | 方法被调用次数 | Client 1500 / Server 10000 |
| 回边计数器 | 循环体执行次数 | 触发 OSR(栈上替换) |
超过阈值 → 提交给 JIT 编译器异步编译 → 编译完成后替换为机器码。
OSR(On-Stack Replacement):方法执行过程中,直接将正在运行的解释版本替换为编译版本,无需等待方法返回。
编译器层级(分层编译)
JDK 8+ 默认开启分层编译(Tiered Compilation),共 5 层:
| 层级 | 执行方式 | 说明 |
|---|---|---|
| 0 | 解释执行 | 无性能数据收集 |
| 1 | C1 编译 | 无 profiling,简单优化 |
| 2 | C1 编译 | 带方法调用次数/回边计数 |
| 3 | C1 编译 | 带完整 profiling(类型、分支等) |
| 4 | C2 编译 | 激进优化,基于 profiling 数据 |
启动 → 0层解释 → 3层C1(收集数据)→ 4层C2(激进优化)
C1 vs C2
| C1(Client 编译器) | C2(Server 编译器) | |
|---|---|---|
| 编译速度 | 快 | 慢 |
| 优化程度 | 中等 | 激进 |
| 适用场景 | 快速启动,短时运行 | 长时间运行的服务端程序 |
| 典型优化 | 内联、去虚化 | 标量替换、锁消除、循环展开 |
主要优化技术
方法内联(Inlining)
将被调用方法的方法体直接嵌入调用处,消除方法调用开销,并为后续优化创造条件。
// 内联前
int add(int a, int b) { return a + b; }
int result = add(x, y);
// 内联后(概念)
int result = x + y;内联条件:方法不超过 35 字节(-XX:MaxInlineSize)、调用频繁、非虚方法或可去虚化。
逃逸分析(Escape Analysis)
分析对象的作用域是否逃出当前方法/线程:
| 逃逸类型 | 说明 | 优化手段 |
|---|---|---|
| 无逃逸 | 对象只在方法内使用 | 栈上分配 / 标量替换 |
| 方法逃逸 | 对象被外部方法引用 | 部分优化 |
| 线程逃逸 | 对象被其他线程引用 | 无法优化 |
栈上分配:无逃逸对象直接分配在栈帧上,方法返回自动回收,减轻 GC 压力。
标量替换:将对象拆散为基本类型字段,直接存入寄存器,完全避免对象创建。
锁消除(Lock Elimination)
逃逸分析确认对象不会被多线程共享时,去掉多余的同步锁:
// StringBuffer 是线程安全的,但 sb 不逃逸,锁会被消除
public String concat(String a, String b) {
StringBuffer sb = new StringBuffer();
sb.append(a);
sb.append(b);
return sb.toString();
}锁粗化(Lock Coarsening)
连续对同一对象加锁/解锁,合并为一次加锁,减少锁操作次数。
循环展开(Loop Unrolling)
减少循环判断次数,提高指令级并行度:
// 展开前:循环 100 次
// 展开后:每次迭代执行 4 个操作体,循环 25 次
去虚化(Devirtualization)
虚方法调用需要通过虚方法表查找,开销大。JIT 根据 profiling 数据,若某个调用点的接收者类型总是同一个类,则直接内联该类的实现(单态内联缓存)。
AOT(Ahead-of-Time)编译
与 JIT 相对,AOT 在运行前将代码编译为机器码:
| JIT | AOT | |
|---|---|---|
| 编译时机 | 运行时 | 运行前 |
| 启动速度 | 慢(需预热) | 快 |
| 峰值性能 | 高(可利用运行时数据) | 较低 |
| 代表 | HotSpot JIT | GraalVM Native Image |
GraalVM Native Image 将 Java 程序编译为独立二进制,启动时间从秒级降至毫秒级,适合 Serverless / 容器场景。
常用 JVM 参数
-XX:+TieredCompilation 开启分层编译(JDK8+ 默认开启)
-XX:CompileThreshold=10000 触发 JIT 的方法调用阈值
-XX:+PrintCompilation 打印 JIT 编译日志
-XX:+DoEscapeAnalysis 开启逃逸分析(默认开启)
-XX:+EliminateLocks 开启锁消除(默认开启)
-XX:MaxInlineSize=35 内联方法的最大字节数