跳到正文
Elaine Blog
返回

解释执行、JIT、分层编译与反优化

JVM 与性能

HotSpot 会先快速启动并收集运行画像,再把热点方法编译成机器码。这个过程解释了为什么 Java 服务需要预热、为什么 短基准会误导,以及为什么同一方法的性能可能在运行中变化。

Table of contents

Open Table of contents

观察一个热点方法

下载JIT 预热样例下载, 编译后运行:

java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining \
  -cp target/classes dev.elaine.jvmlab.JitWarmupSample 20000

输出中会出现方法名、编译层级和状态。日志较多时先过滤 JitWarmupSample,不要把某个编译编号当成稳定 API。

解释器、C1 与 C2 如何协作

解释器逐条执行字节码,启动快,也能收集分支和调用类型等画像。HotSpot 的**分层编译(Tiered Compilation)**结合 C1 与 C2:C1 较快地产生基础机器码和画像,C2 对足够热点的代码执行更激进优化。

常见优化包括方法内联、常量折叠、范围检查消除、逃逸分析和死代码消除。优化依赖运行时假设,例如“这个调用点只出现 一种实现”。

反优化不是错误

当新加载的类或运行数据推翻假设时,JVM 可以让已编译代码失效,回到解释执行或低层编译,再用新画像重新编译。这叫 反优化(Deoptimization)

例如,调用点长期只有一个实现时,JIT 可能直接内联;后来出现第二个实现,原机器码就可能不再安全。反优化保障语义 正确,但会造成短时性能波动。它不是内存泄漏,也不是“JIT 编译失败”。

预热如何影响测量

下面的测量方式不可靠:

long started = System.nanoTime();
someMethod();
System.out.println(System.nanoTime() - started);

第一次调用混入类加载、解释执行、编译和缓存冷启动。JIT 还可能删除未消费结果。微基准应使用 JMH,让框架负责预热、 多次 Fork、结果消费和统计。

服务压测则要定义稳定期:达到目标吞吐、编译活动下降、连接池就绪和缓存状态明确后再采集指标。记录启动后第 1 分钟和 第 30 分钟的差异,比只报告一个平均值更有意义。

生产诊断建议

优先用 JFR 的执行采样和编译事件观察热点,不要长期开启海量 Print* 日志。若修改 CompileThreshold、关闭分层编译或 禁止内联,必须通过相同负载复测吞吐、P99、CPU 和启动时间。

下一步

阅读GC 基础与三色标记,理解对象不可达后 JVM 如何安全回收。


分享这篇文章:

上一篇
Class 文件、常量池与字节码指令
下一篇
GC 基础与三色标记