bigywater.tech
Java · 241

分层编译:解释器与 JIT 如何接力

JVM 先解释执行,再用 C1 快速编译,最后用 C2 深度优化。这篇用 PrintCompilation 看一个方法的层级变化,量出热身曲线,并对比解释器、只用 C1、默认分层与只用 C2 的总耗时。

137 → 16 ns
同一个方法每次调用的耗时:第 1 批 → 稳定后
401 ms
默认分层模式 2800 万次调用耗时(-Xint 为 10742 ms)
200 / 5000
Tier3 / Tier4 调用次数阈值

PrintCompilation:编译历史

实测 · 本机执行输出
  16 5 3 Main::hot (31 bytes)
  16 6 3 Main::mid (12 bytes)
  16 7 4 Main::hot (31 bytes)
  17 5 3 Main::hot (31 bytes) made not entrant
  17 8 4 Main::mid (12 bytes)
  18 6 3 Main::mid (12 bytes) made not entrant
  19 9 % 3 Main::runBatch @ 5 (30 bytes)
  19 10 3 Main::runBatch (30 bytes)
  19 11 % 4 Main::runBatch @ 5 (30 bytes)
  25 11 % 4 Main::runBatch @ 5 (30 bytes) COMPILE SKIPPED: Too many clones required in clone_for_use_outside_loop in partial peeling (retry at different
  25 9 % 3 Main::runBatch @ 5 (30 bytes) made not entrant
  25 12 % 1 Main::runBatch @ 5 (30 bytes)
  (列:时间 ms、编译序号、标志、层级、方法、字节码大小;% 表示 OSR 编译,「made not entrant」表示旧版本被替换)

热身曲线

实测 · 本机执行输出
  每批 2000 次 mid() 调用的平均耗时(ns/次):第 1 批 137;第 2–3 批 90;第 4–10 批 37;第 11–30 批 27;第 31–100 批 17;第 101–300 批 16.2
  前 12 批的 ns/次:137 87 92 85 34 28 28 28 28 28 28 28

四种编译模式

实测 · 本机执行输出
  [-Xint(只用解释器)] 3 次运行的中位:前 10 万次调用 38.1 ms;中间 2800 万次 10742 ms(范围 10635–10766);最后 200 万次 767.7 ms,383.9 ns/次
  [-XX:TieredStopAtLevel=1(只用 C1)] 3 次运行的中位:前 10 万次调用 3.2 ms;中间 2800 万次 573 ms(范围 572–579);最后 200 万次 41.2 ms,20.6 ns/次
  [默认(分层:解释器 → C1 → C2)] 3 次运行的中位:前 10 万次调用 3.3 ms;中间 2800 万次 401 ms(范围 399–404);最后 200 万次 28.5 ms,14.3 ns/次
  [-XX:-TieredCompilation(只用 C2)] 3 次运行的中位:前 10 万次调用 4.2 ms;中间 2800 万次 760 ms(范围 755–768);最后 200 万次 50.3 ms,25.1 ns/次

阈值与编译线程

实测 · 本机执行输出
  CICompilerCount                = 4
  TieredStopAtLevel              = 4
  Tier3InvocationThreshold       = 200
  Tier3BackEdgeThreshold         = 60000
  Tier4InvocationThreshold       = 5000
  Tier4BackEdgeThreshold         = 40000
  Tier3MinInvocationThreshold    = 100
  Tier3CompileThreshold          = 2000
  Tier4CompileThreshold          = 15000
  Tier0ProfilingStartPercentage  = 200
  这个小程序(约 0.4 秒)一共编译的方法数,按层级: {'0': 17, '1': 4, '3': 19, '4': 5} ;「made not entrant」4 次

分步演示

分层编译
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS 10 核(CICompilerCount=4)
    • 没有验证:ZGC 之外 GC 对编译的影响、-XX:Tier*Threshold 调小后的效果、编译队列长度与编译延迟、Graal/JVMCI 编译器、只用 C2 比默认慢的原因(这里是一个简单循环里的现象,不代表一般情况)、C2 OSR 被跳过的具体原因
    京ICP备2026067502号-1京公网安备11010502063487号