JMH 基准测试

共 18 题
📑 题目列表 18 题
#
★★★

1. JMH 的 @State 与线程安全

JMH 的 @State 与线程安全如何理解?

  • @State 作用域
  • 线程安全
  • 基准隔离

JMH 的 @State 注解定义基准共享的状态对象,作用域有:Scope.Benchmark(所有线程共享同一实例)、Scope.Thread(每个线程独立实例)、Scope.Group(线程组内共享)。@State 决定基准中状态变量的共享范围,影响线程安全:Scope.Thread 每个线程独立,无共享竞争;Scope.Benchmark 多线程共享实例,需线程安全(如用不可变对象、synchronized、原子类),否则产生竞争影响基准结果。@State 对象在基准前初始化(@Setup),在基准后清理(@TearDown)。理解 @State 作用域与线程安全是编写正确 JMH 基准的关键,避免共享状态竞争导致结果失真。

@State 作用域决定共享方式,Scope.Thread 隔离、Scope.Benchmark 共享。理解作用域与线程安全是正确基准的核心。

#
★★★

2. JMH 的 profiler,perfasm、stack、gc 的用法

JMH 的 profiler:perfasm、stack、gc 如何应用?

  • perfasm 汇编
  • stack 栈剖析
  • gc 剖析

JMH 的 profiler(-prof 参数):1) perfasm:结合 Linux perf 与汇编,分析基准方法的汇编级优化(JIT 生成的汇编、内联、指令),定位低级热点;2) stack:采样调用栈,定位基准内热点方法(CPU 热点,类似火焰图);3) gc:报告基准的 GC 活动(分配速率、GC 次数、GC 时间),评估基准的内存分配与 GC 影响。此外还有 async(async-profiler)、jfr、classloader 等。应用:perfasm 深入 JIT 汇编优化,stack 定位调用热点,gc 评估 GC 开销。理解各 profiler 用途可深入分析基准性能。

perfasm 看汇编、stack 看热点、gc 看 GC。理解 profiler 用途是深度分析 JMH 基准的关键。

#
★★★

3. JMH 的陷阱,死代码消除、常量折叠、伪共享、预热不足

JMH 的陷阱:死代码消除、常量折叠、伪共享、预热不足如何避免?

  • 死代码消除
  • 常量折叠
  • 伪共享与预热不足

JMH 的常见陷阱:1) 死代码消除:基准方法的结果未被消费,JIT 会删除无用计算,导致结果失真,需用 Blackhole 消费结果;2) 常量折叠:基准内参数为常量,JIT 在编译期折叠计算,结果失真,需用 @Param 或不可折叠的输入;3) 伪共享:多线程基准访问同一缓存行上的不同变量,缓存一致性导致性能下降,需注意变量布局(@Contended);4) 预热不足:未充分预热,JIT 未就绪,结果偏低,需足够 warmup 迭代。避免:用 Blackhole 消费结果、避免常量折叠(动态输入)、隔离伪共享变量、充分预热。理解这些陷阱是 JMH 基准可信的前提。

死代码消除、常量折叠、伪共享、预热不足是 JMH 四大陷阱。正确配置 Blackhole/输入/布局/预热才能获得可信结果。

#
★★

4. JMH 的注解处理器如何生成基准类(@Benchmark 方法的包装与黑盒)

JMH 的注解处理器如何生成基准类(@Benchmark 方法的包装与黑盒)?

  • 注解处理器
  • 基准类生成
  • 黑盒机制

JMH 使用注解处理器(annotation processor)在编译时读取 @Benchmark、@State、@Param 等注解,生成对应的基准类(如 Benchmark_xxx 的生成类),其内部包装 @Benchmark 方法:生成调用循环、测量逻辑、Blackhole 消费、状态管理。黑盒(Blackhole)机制把基准方法的结果由 Blackhole 消费(黑盒),防止 JIT 死代码消除,同时 Blackhole 内部避免被优化。生成的基准类处理:预热、迭代、测量、fork 的控制逻辑。注解处理器把这些元数据转化为可测量、防优化的基准代码。理解注解处理器生成机制与黑盒,是理解 JMH 如何保证基准正确性的关键。

注解处理器生成包装基准类,用 Blackhole 黑盒防死代码消除。理解生成机制与黑盒是 JMH 可信的关键。

#
★★

5. JMH 与 CI 集成,基准回归测试如何落地

JMH 与 CI 集成:基准回归测试如何做?

  • JMH 基准测试
  • CI 集成
  • 回归检测

JMH 与 CI 集成的基准回归测试:1) 在 CI 中运行 JMH 基准(mvn test 或单独 profile 运行),对关键方法测性能;2) 与基线对比:保存历史基准结果(如基线报告),对比本次结果,检测性能回归(吞吐下降、延迟上升超阈值);3) 用统计显著性(置信区间)判断回归是否真实,避免噪声;4) 作为质量门禁:性能回归阈值(如吞吐下降 >5%)则构建失败;5) 注意 CI 环境资源不稳定,需控制变量(固定 fork、预热、机器类型),或只在专用性能环境跑。工具:可用 JMH 的 JSON 结果 + 自定义对比脚本,或用持续性能基准平台。价值是"每次变更自动检测性能回归"。

JMH 回归测试需与基线对比 + 统计显著性判断 + 门禁。理解 CI 环境变量控制与基线对比是关键。

#
★★

6. JMH 的 @BenchmarkMode,Throughput、AverageTime、SampleTime、SingleShotTime

JMH 的 @BenchmarkMode:Throughput、AverageTime、SampleTime、SingleShotTime 如何理解?

  • Throughput 吞吐
  • AverageTime 平均时间
  • SampleTime 采样时间

@BenchmarkMode 指定基准的测量模式:1) Throughput:每秒操作次数(吞吐量,如 ops/s);2) AverageTime:每次操作的平均时间(如 ns/op);3) SampleTime:采样每次操作的时间分布(可看分位,如 p99);4) SingleShotTime:单次执行(不预热循环,适合冷启动/一次性操作)。通过 @BenchmarkMode 的 Mode 类型选择:吞吐/平均适合常规基准,采样时间适合看分布与尾延迟,单次适合非循环场景(如一次类加载)。可通过 @BenchmarkMode({Mode.Throughput, Mode.AverageTime}) 组合多个模式。理解各模式语义是选择正确基准度量的关键。

@BenchmarkMode 决定测量维度(吞吐、平均、采样、单次)。理解各模式语义按场景选择度量是关键。

#
★★

7. JMH 的 @CompilerControl 与内联控制

JMH 的 @CompilerControl 与内联控制如何理解?

  • @CompilerControl 注解
  • 内联控制
  • 编译优化

@CompilerControl 控制 JIT 编译器的优化行为,主要用于内联控制:1) INLINE:强制方法内联;2) DONT_INLINE:禁止方法内联;3) EXCLUDE:排除方法(不参与编译,解释执行);4) COMPILE:强制编译。用于基准测试中:控制基准方法/辅助方法的内联,避免内联行为导致结果失真,或验证不同内联策略的性能。例如用 @CompilerControl(CompilerControl.Mode.DONT_INLINE) 禁止基准方法内联,观察其真实性能。理解 @CompilerControl 可控制 JIT 优化,隔离或验证编译行为对基准的影响。

@CompilerControl 控制内联与编译优化,隔离/验证 JIT 行为。理解其模式是精确控制基准编译的关键。

#
★★

8. JMH 的 Blackhole 与常量折叠防护

JMH 的 Blackhole 与常量折叠防护如何理解?

  • Blackhole 作用
  • 常量折叠防护
  • 防优化

JMH 的 Blackhole 用于消费基准方法的结果,防止 JIT 死代码消除;同时防常量折叠:基准中若传入常量参数,JIT 会在编译期折叠计算(如 int x = 2 * 3 直接算 6),导致基准退化。Blackhole 防护:1) 用 Blackhole.consume(result) 消费结果,防止结果被删除;2) 用 @Param 传入运行时参数(避免常量)、或用不可预测的输入(如从状态读取、随机),防止常量折叠;3) Blackhole 内部实现避免被优化。通过 Blackhole 与动态输入,保证基准测量的是真实计算而非编译期折叠的结果。理解 Blackhole 与常量折叠防护是 JMH 可信的核心。

Blackhole 防死代码消除,动态输入防常量折叠。理解防优化机制是 JMH 基准正确性的关键。

#
★★

9. JMH 的黑洞(Blackhole)与死代码消除,为什么基准方法返回值必须被消费?

JMH 的黑洞(Blackhole)与死代码消除:为什么基准方法返回值必须被消费?

  • 死代码消除
  • Blackhole 消费
  • 基准正确性

JMH 基准方法返回值必须被消费,因为 JIT 编译器会做死代码消除(dead code elimination):如果方法的返回值未被使用,JIT 会认为计算是无用的,删除相关计算,导致基准测不到真实工作量(测出来的近乎 0)。Blackhole 通过 consume 方法"消费"结果,使 JIT 认为结果被使用,无法消除计算,从而测到真实性能。Blackhole 内部实现(如 volatile 内存写入、特殊指令)避免被 JIT 优化掉。因此基准方法必须把结果传给 Blackhole(return 值的基准方法,JMH 自动用 Blackhole 消费;若方法无返回值但内部有副作用,需确保副作用不被消除)。理解死代码消除与 Blackhole 是 JMH 基准正确性的基础。

返回结果必须被消费以规避死代码消除。Blackhole 让 JIT 无法删除计算,测到真实性能。

#
★★

10. JMH 的 Fork/Warmup/Iteration 配置,多轮预热与统计(误差/置信区间)如何影响结果可信度?

JMH 的 Fork/Warmup/Iteration 配置:多轮预热与统计(误差/置信区间)如何影响结果可信度?

  • Fork 配置
  • Warmup 预热
  • Iteration 与统计

JMH 的 Fork/Warmup/Iteration 配置影响结果可信度:1) Fork:多 fork 在独立 JVM 运行,隔离 JIT/GC/类加载状态,多次 fork 的平均与误差反映跨进程稳定性;2) Warmup:预热迭代让 JIT 充分编译、到达稳态,预热不足则结果偏低且波动;3) Iteration:测量迭代次数与时长,迭代越多统计越稳定,误差(score error)与置信区间(conf interval)反映结果可靠度;误差小、置信区间窄说明结果可信。可信度:多 fork + 足量 warmup + 足够 iteration 使 JIT 稳态、统计稳定,误差小。置信区间重叠的结果无法区分性能差异。理解配置与统计可评估基准可信度。

Fork 隔离、Warmup 稳态、Iteration 统计稳定性共同决定可信度。理解误差/置信区间是评估结果的关键。

#
★★

11. JMH 的基准类型,吞吐量/平均时间/采样时间的适用场景如何?

JMH 的基准类型:吞吐量/平均时间/采样时间的适用场景如何选择?

  • 吞吐量场景
  • 平均时间场景
  • 采样时间场景

JMH 基准类型(@BenchmarkMode)适用场景:1) 吞吐量(Throughput):关注系统单位时间能处理多少操作(如 QPS、每秒处理数),适合评估容量/扩展性,如服务每秒处理请求数;2) 平均时间(AverageTime):关注单次操作的平均耗时,适合评估单次操作成本(如某算法一次耗时),用于对比实现;3) 采样时间(SampleTime):关注操作耗时分布(分位、尾延迟),适合评估延迟分布(p99 尾延迟),关注性能波动。选择:容量/吞吐用 Throughput,单次成本用 AverageTime,延迟分布/尾延迟用 SampleTime。也可组合。理解场景与模式匹配是选型关键。

吞吐量看容量、平均时间看单次成本、采样时间看延迟分布。按测量目标选择基准模式。

#
★★

12. @OperationsPerInvocation 在多操作基准(批量处理)中的统计含义

@OperationsPerInvocation 在多操作基准(批量处理)中的统计含义是什么?

  • @OperationsPerInvocation 定义
  • 批量处理统计
  • 正确性

@OperationsPerInvocation 声明一次基准方法调用代表多少次"操作"(operation),用于批量处理基准。例如基准方法内部循环处理 N 个元素,实际是 N 次操作,标注 @OperationsPerInvocation(N),JMH 的吞吐/平均时间会按"每次操作"计算(而非每次调用),使统计正确反映单次操作成本。不标注时,JMH 把一次调用视为一次操作,批量处理的吞吐/时延会被高估/低估。@OperationsPerInvocation 让多操作基准的统计归一化,正确反映每操作成本。理解其统计含义是批量基准正确性的关键。

@OperationsPerInvocation 归一化批量操作的统计,使基准反映每操作成本。理解其语义是批量基准正确性关键。

#

13. JMH 的参数化,@Param 与命令行参数

JMH 的参数化:@Param 与命令行参数如何理解?

  • @Param 参数化
  • 命令行参数
  • 用法

JMH 的参数化:@Param 注解在基准中声明参数集合,编译时生成各参数组合的基准,运行时自动为每个参数值运行测量。命令行参数:运行 JMH 时可用 -p 指定参数(如 -p size=1,10,100),优先级高于 @Param;也可用 -bm 指定模式、-f 指定 fork、-w/-i 指定 warmup/iteration、-t 指定线程,覆盖注解配置。@Param 用于编译期声明参数维度,命令行参数用于运行时覆盖/补充。两者结合可灵活配置参数化基准。理解 @Param 与命令行参数是 JMH 参数化测试的关键。

@Param 声明参数,命令行参数运行时覆盖。理解两者结合可灵活配置参数化基准。

#

14. JMH 与生产环境差异,JIT 编译、分支预测与 GC 对微基准结果的误导,如何用宏观基准交叉验证?

JMH 与生产环境差异:JIT 编译、分支预测与 GC 对微基准结果的误导,如何用宏观基准交叉验证?

  • JIT 编译差异
  • 分支预测与 GC
  • 宏观基准交叉验证

JMH 微基准与生产环境存在差异,可能误导:1) JIT 编译:微基准中 JIT 可能内联/优化特定小方法,而生产大代码中该路径未被优化,微基准结果可能偏乐观;2) 分支预测:微基准的分支模式(如全真/全假)与生产混合分支不同,分支预测命中率差异导致性能差异;3) GC:微基准可能不触发 GC 或触发频率与生产不同,未反映真实 GC 压力;4) 数据规模/缓存:微基准数据小而热缓存,生产数据大缓存 miss。宏观基准交叉验证:用模拟生产的宏观基准(接近生产负载、多方法、真实数据规模)验证微基准结论,或用压测/真实应用验证,避免微基准的过度乐观结论。核心是"微基准定方向,宏观基准验证"。

JIT/分支预测/GC 使微基准与生产有差异,需宏观基准交叉验证。理解差异与验证方法是基准可信的关键。

#

15. JMH 的 @State 与 @BenchmarkMode,基准隔离与 JIT 预热的关系如何?

JMH 的 @State 与 @BenchmarkMode:基准隔离与 JIT 预热的关系如何?

  • @State 隔离
  • @BenchmarkMode 与预热
  • JIT 预热关系

JMH 的 @State 提供基准状态隔离(Scope.Thread 每线程独立实例,避免共享竞争),@BenchmarkMode 决定测量模式。基准隔离与 JIT 预热关系:JIT 预热(warmup 迭代)让状态对象与代码被 JIT 编译优化,到达稳态;@State 的实例在预热与测量间被复用/JIT 优化,若 @State 共享(Scope.Benchmark)在多线程下可能被 JIT 的不同优化影响。基准隔离(@State 作用域)与预热共同保证:隔离避免状态竞争、预热让 JIT 达稳态,两者配合使测量反映稳定性能。@State 的 setup 在预热前执行,JIT 预热后状态稳定。理解隔离与预热关系是 JMH 可信基准的关键。

@State 隔离状态、预热让 JIT 稳态,两者配合保证测量稳定。理解隔离与预热的关系是基准可信关键。

#

16. JMH 结果的可比性,机器差异、JIT 状态与基准漂移如何控制?

JMH 结果的可比性:机器差异、JIT 状态与基准漂移的控制如何做?

  • 机器差异
  • JIT 状态
  • 基准漂移

JMH 结果可比性受机器差异、JIT 状态、基准漂移影响:1) 机器差异:不同机器(CPU/内存/OS)结果不同,比较需在相同/同类机器上,或归一化(如按 CPU 频率);2) JIT 状态:JIT 编译状态(编译级别、是否内联)影响性能,需固定预热与编译配置,确保同 JIT 状态比较;3) 基准漂移:长时间运行中 JIT 重新编译、GC、CPU 状态变化导致结果漂移,需控制(多 fork、统计置信区间、固定环境)。控制:固定机器/环境、固定预热与编译、多 fork 平均、用置信区间判断差异、避免长时间漂移。比较时确保"同一环境 + 稳态 + 统计显著"才可比。理解可比性控制是 JMH 对比结果可信的关键。

机器差异、JIT 状态、基准漂移影响可比性。控制环境、稳态、统计显著性是可比的关键。

#

17. JMH 的 @Fork 与 @Warmup,隔离 JIT 状态与结果稳定性的作用如何?

JMH 的 @Fork 与 @Warmup:隔离 JIT 状态与结果稳定性的作用如何?

  • @Fork 隔离
  • @Warmup 预热
  • 结果稳定性

JMH 的 @Fork 在独立 JVM 进程中运行基准,隔离 JIT 状态(每个 fork 重新编译、独立类加载与 GC),避免跨基准的 JIT/GC 状态污染,从而提升结果稳定性与可信度;@Warmup 提供预热迭代,让 JIT 在测量前充分编译优化、到达稳态(避免测量阶段 JIT 仍在编译导致波动)。两者作用:@Fork 隔离进程与 JIT 状态(减少跨进程干扰),@Warmup 预热(减少测量期 JIT 活动),共同提升结果稳定性。多 fork + 充分 warmup 使测量稳态、误差小、结果稳定。理解两者的配合是 JMH 稳定结果的关键。

@Fork 隔离 JIT 状态,@Warmup 预热稳态,共同提升结果稳定性。理解配合是 JMH 可信基准的关键。

#

18. JMH 的 @Setup/@TearDown 生命周期(Trial/Iteration/Invocation)与使用限制

JMH 的 @Setup/@TearDown 生命周期(Trial/Iteration/Invocation)与使用限制如何理解?

  • @Setup/@TearDown 生命周期
  • Trial/Iteration/Invocation 级别
  • 使用限制

JMH 的 @Setup/@TearDown 定义基准前后或测量周期的初始化/清理方法,生命周期级别:1) Trial(Level.Trial):整个基准(所有 fork/迭代)前后执行一次,适合重初始化(如加载大数据);2) Iteration(Level.Iteration):每个迭代前后执行,适合每迭代重置;3) Invocation(Level.Invocation):每次操作调用前后执行,适合精确控制(但开销大、影响基准)。使用限制:@Setup/@TearDown 方法不能有参数(除 @State 参数)、不能是静态、@State 用 @Setup 标注在 @State 类上;Invocation 级别避免在 @Setup 中做重操作(影响测量)。理解生命周期级别与限制是正确使用 JMH 状态初始化的关键。

@Setup/@TearDown 按 Trial/Iteration/Invocation 级别执行,Invocation 开销大。理解级别与限制是正确初始化基准的关键。