外观
一句话答案
JIT 将热点代码(调用超过阈值)编译为本地机器码,关键优化:方法内联、逃逸分析(栈上分配/标量替换/锁消除)。
核心要点
为什么需要 JIT(Just-In-Time)编译:
Java 代码的执行过程:
.java → javac 编译 → .class(字节码)→ JVM 解释执行
问题:解释执行逐条翻译字节码为机器码,速度慢
JIT 的作用:
在运行时将"热点代码"(频繁执行的代码)直接编译为本地机器码
后续执行直接运行机器码,跳过解释过程 → 速度接近 C/C++热点探测(Hot Spot Detection):
JVM 通过计数器判断代码是否是"热点":
① 方法调用计数器:统计方法被调用的次数
默认阈值:Client 模式 1500 次 / Server 模式 10000 次
超过阈值 → 触发 JIT 编译
② 回边计数器:统计循环体执行的次数
循环体执行次数超过阈值 → 触发 OSR(On-Stack Replacement,栈上替换)
即:循环执行到一半,切换为编译后的机器码继续执行C1 和 C2 编译器(分层编译):
| 维度 | C1(Client Compiler) | C2(Server Compiler) |
|---|---|---|
| 优化程度 | 轻量级优化(编译快) | 重量级优化(编译慢,但代码更快) |
| 编译速度 | 快(毫秒级) | 慢(可能几十到几百毫秒) |
| 优化手段 | 方法内联、常量折叠、简单逃逸分析 | 全部 C1 优化 + 标量替换、循环展开、向量化等 |
| 适用阶段 | 程序启动期(快速编译,尽快变快) | 程序稳态期(充分优化,达到最快) |
分层编译(Tiered Compilation,JDK 8+ 默认开启):
Level 0: 解释执行(收集 profile 数据)
Level 1: C1 编译,不带 profiling
Level 2: C1 编译,带有限的 profiling
Level 3: C1 编译,带完整的 profiling(为 C2 收集数据)
Level 4: C2 编译(最高优化级别)
典型路径:L0 → L3(C1 + profiling)→ L4(C2 深度优化)逆优化(Deoptimization):
JIT 编译基于运行时的假设进行激进优化:
例:"这个方法只有一个实现类" → 内联该实现
如果假设被打破(如动态加载了新的实现类):
→ JIT 必须"逆优化",退回到解释执行
→ 重新收集 profile 数据,再次编译
典型触发场景:
① 新类加载导致虚方法表变化
② 分支预测假设被推翻
③ 数组越界等异常路径被触发追问与易错
追问方向:
- 什么是热点代码?怎么判定?
- 逃逸分析失败的场景?
- C1 和 C2 编译器的区别?
易错点:
- ❌ 所有对象都能栈上分配——必须通过逃逸分析判定不逃逸
- ❌ 混淆 JIT 编译优化和代码优化
💡 记忆锚点
JIT像翻译官升级:先逐句口译(解释执行),哪段台词念了上万遍(热点代码)就背下来直接说(编译为机器码)。C1快译粗糙版应急,C2精雕细琢出终版。假设被推翻(新实现类加载)就回退重来(逆优化)。