bigywater.tech
Java · 147

JIT 编译:热点代码如何变成本地机器码

热点代码是怎么变成机器码的?这篇用 PrintCompilation 把一个方法的「解释 → C1 → C2」过程和每一档的速度逐批量出来(分层编译的阈值、去优化、内联、OSR 各有后面的专门篇目)。

300 → 57 → 8.7 ns
同一个方法在解释、C1、C2 阶段的平均耗时/次
24 倍
默认设置比纯解释器 -Xint 快的倍数
made not entrant
第 4 层版本出现后,第 3 层版本被淘汰

编译时间线

实测 · 本机执行输出
  批  1:调用   100 次,平均   300.0 ns/次
  31 121 3 Main::work (31 bytes)
  批  2:调用   100 次,平均   265.4 ns/次
  批  3:调用   100 次,平均    57.1 ns/次
  批  4:调用   100 次,平均    57.9 ns/次
  批  5:调用   100 次,平均    57.5 ns/次
  批  6:调用   100 次,平均    49.6 ns/次
  批  7:调用   100 次,平均    55.8 ns/次
  批  8:调用   100 次,平均    47.9 ns/次
  32 130 4 Main::work (31 bytes)
  批  9:调用 20000 次,平均    43.4 ns/次
  32 121 3 Main::work (31 bytes) made not entrant
  批 10:调用 20000 次,平均    21.7 ns/次
  批 11:调用 20000 次,平均    19.2 ns/次
  批 12:调用 20000 次,平均    17.9 ns/次
  批 13:调用 20000 次,平均    12.7 ns/次
  批 14:调用 20000 次,平均     8.7 ns/次

四种运行方式

实测 · 本机执行输出
方式                                      总耗时中位数(ms)      平均每次(ns)
-Xint(纯解释器)                                    554         184.7
-XX:TieredStopAtLevel=1(只 C1)                   31          10.3
默认(分层编译 C1 + C2)                                23           7.7
-XX:-TieredCompilation(只 C2)                    24           8.0

分步演示

JIT 编译
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:编译触发的具体阈值(Tier3InvocationThreshold 等)的取值、反汇编出的机器码(没有装 hsdis)、后台编译线程数的影响、其它 JDK 版本的行为;批 1 的耗时包含类加载和解释器的启动开销,不全是解释执行本身
    京ICP备2026067502号-1京公网安备11010502063487号