bigywater.tech
Java · 309

JMH 预热与 Fork:为什么要启动多个 JVM

JMH 为什么默认给每个基准启动一个新 JVM?预热多少才够?不同 fork 之间的结果能差多少?这篇设计一个共用调用点的基准,让 fork 的作用在数字里出现。

4.6 倍
b3 在 -f 0(共用 JVM)下比 -f 1 慢的倍数:2042.844 对 447.592 ns/op
616.885 → 436.2 ns
10 毫秒迭代、无预热时第 1 次与第 2 次的 ns/op
≤ 1.7%
本例 5 个 fork 之间均值的最大差异

fork 与 profile 污染

实测 · 本机执行输出
  -f 0(全部在 JMH 主进程里,共用一个 JVM 的 JIT 状态):
    b1_monomorphic            441.452 ± 74.268 ns/op
    b2_polymorphic           2727.982 ± 18.717 ns/op
    b3_monomorphicAgain      2042.844 ± 94.846 ns/op
  -f 1(每个基准一个新的 fork):
    b1_monomorphic            447.773 ± 7.292 ns/op
    b2_polymorphic           2728.227 ± 35.121 ns/op
    b3_monomorphicAgain       447.592 ± 48.639 ns/op

预热

实测 · 本机执行输出
  没有预热时每次迭代的 ns/op: [445.787, 449.392, 445.498, 443.512, 447.084, 447.856]
  -wi 3 -i 3 时全部 6 次迭代(前 3 次是预热)的 ns/op: [445.56, 447.668, 447.553, 444.893, 443.453, 446.104]
  把每次测量缩短到 10 毫秒、没有预热(-r 10ms)时每次迭代的 ns/op: [616.885, 436.2, 430.678, 441.073, 452.781, 450.952, 447.705, 459.432, 436.112, 449.086]

fork 之间的差异

实测 · 本机执行输出
  h_identityHashMapLookups: 各 fork 的均值 [2321.92, 2345.58, 2312.81, 2315.21, 2352.59];最小 2312.81,最大 2352.59,最大/最小 = 1.017;JMH 汇总 [('h_identityHashMapLookups', 2329.622, 18.342)]
  b2_polymorphic: 各 fork 的均值 [2728.24, 2728.71, 2729.78, 2731.23, 2732.95];最小 2728.24,最大 2732.95,最大/最小 = 1.002;JMH 汇总 [('b2_polymorphic', 2730.182, 3.951)]

分步演示

JMH 预热与 fork
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,JMH 1.37,Apple Silicon;每组数字是一次运行,fork 差异只测了两个基准
    • 没有验证:-f 0 下污染的 JIT 层面机制(没有用 -XX:+PrintCompilation / PrintInlining 看)、更多种类的 fork 间差异(如内存布局、哈希随机化)、JDK 27 的行为、线程数大于 1 的情况
    京ICP备2026067502号-1京公网安备11010502063487号