解释器、分层编译与热点探测
HotSpot 启动方法时可以先解释字节码,同时记录调用次数、分支方向和接收者类型。代码变热后,JIT 在后台把它编译为机器码;分层编译用不同优化成本的层级平衡启动速度、画像质量和峰值性能。
1. 解释执行让代码可以立即运行
类完成加载后,解释器可以逐条执行字节码,无需先等待整套应用编译。它适合:
- 只执行一次的启动或错误路径。
- 收集分支和类型等运行时画像。
- 编译任务尚未完成时继续推进程序。
解释器的单次执行成本通常高于已优化机器码,但“所有方法启动前先编译”也会把大量冷代码的编译成本提前。HotSpot 根据实际执行热度决定投资在哪些方法上。
2. 热点来自方法调用和循环回边
JIT 关注两类重复执行:
- 方法被调用很多次。
- 一个方法中的循环回边执行很多次。
长循环所在方法即使只调用一次,也可能在循环仍运行时触发 OSR(On-Stack Replacement),让当前栈帧从解释执行切到已编译循环入口。
不要把“调用一万次就编译”当成固定规则。阈值会受分层编译、JDK 版本、负载和 JVM 参数影响,队列压力也会改变编译时机。
3. 分层编译逐步增加优化投入
常见 HotSpot 分层可以概括为:
解释器 → 带画像的快速编译 → 高优化编译
C1 编译速度快,可生成带 profiling 的代码;C2 编译更慢,使用收集到的类型和分支画像做激进优化。HotSpot 日志常显示 0—4 层级,但具体层级路径属于实现细节。
java -XX:+PrintCompilation -jar app.jar
观察时关注方法、层级、是否 OSR、代码是否被标为 not entrant。单条编译记录只能证明生成过版本,不证明请求一直在使用它。
4. 运行时画像支持投机优化
若某个接口调用在实际运行中长期只看到一种实现,JIT 可以先为这个常见类型生成直接调用和内联路径,同时保留假设检查。
long total(PriceRule rule, Order order) {
return rule.calculate(order);
}
源码无法告诉 JIT rule 的实际类型分布,运行时画像可以。当后来加载新实现或调用点变得多态,原假设可能失效,JVM 会去优化或重新编译。
5. 编译在线程和 Code Cache 中消耗资源
JIT 编译任务由编译线程执行,机器码保存在 Code Cache。应用启动或流量突增时,类加载、JIT、GC 和业务可能同时争抢 CPU。
Code Cache 满或编译队列积压会影响优化进度。诊断可以结合:
jcmd <pid> Compiler.queue
jcmd <pid> Compiler.codecache
jcmd <pid> Compiler.perfmap
命令是否可用及输出取决于目标 JDK。JFR 的 Compilation、Code Cache 和 Deoptimization 事件更适合建立时间线。
6. 预热是业务负载逐步形成画像的过程
预热不只是“循环调用固定次数”。稳定状态通常包括:
- 主要类已经加载、初始化。
- 常用路径已编译到合适层级。
- 缓存与连接池进入稳定状态。
- GC 周期和 live set 达到可比较阶段。
基准测试要说明测冷启动、预热过程还是稳态吞吐。把前几次请求直接丢掉可能掩盖实际冷启动目标;把冷数据混入稳态平均值也会回答另一个问题。
7. 常见问题
7.1 Java 是解释执行还是编译执行
源码先由 javac 编译为字节码;运行时可以解释字节码,也可以由 JIT 编译为机器码,两者在同一进程中共存。
7.2 -Xcomp 能否让程序更快
它倾向于先编译方法,常用于实验与诊断,不保证业务更快。缺少充分运行画像时,编译质量可能下降,启动编译成本也会增加。
7.3 方法已经编译过,为什么后来又编译一次
热度、画像或优化层级变化后,JVM 可以生成更高层级版本;旧版本也可能因假设失效而去优化并被替换。
8. 面试题
8.1 Java 是解释执行还是编译执行
出现公司:去哪儿、拼多多
考察重点
javac、解释器与 JIT 的不同阶段。- 热点探测和分层编译。
- 冷启动与峰值性能的权衡。
相关内容:第 1 节“解释执行让代码可以立即运行”至第 4 节“运行时画像支持投机优化”。
参考回答
Java 源码先编译为 Class 字节码。HotSpot 运行时可以先解释执行,让方法立即开始并收集调用、分支和类型画像;热点方法再通过 C1/C2 等层级编译为机器码,长循环还可以用 OSR 在方法执行中切换。
所以它不是简单二选一。解释器降低冷代码的前置成本,JIT 利用真实画像提高热点路径峰值性能。
8.2 HotSpot 怎样判断热点代码
出现公司:去哪儿、京东数科
考察重点
- 方法调用计数、循环回边与 OSR。
- 编译阈值不是固定跨版本常量。
- 编译队列和运行时画像。
相关内容:第 2 节“热点来自方法调用和循环回边”至第 5 节“编译在线程和 Code Cache 中消耗资源”。
参考回答
HotSpot 根据方法调用和循环回边等执行热度触发编译,并收集接收者类型、分支等画像。长循环可以在方法尚未返回时通过 OSR 编译当前热点。分层编译会先生成较快的低层级代码,再用完整画像生成高优化版本。
阈值由 JVM 版本、层级和负载策略决定,不应回答成永远执行某个固定次数。