jmh是专为jvm微基准测试设计的官方工具,通过进程隔离、多轮预热、统计采样和反优化保护实现纳秒级精度,避免jit编译、死代码消除、gc干扰等导致的测量失真。

Java 单元测试本身不负责性能压力基准测试——它专注逻辑正确性,而性能压力基准测试需要专用工具和隔离环境。直接在 JUnit 里用 System.nanoTime() 测方法耗时,结果不可信:JIT 编译未生效、死代码被消除、GC 干扰、预热缺失,都会让数字失真。
要真正做性能压力基准测试,得用 JMH(Java Microbenchmark Harness),它是 OpenJDK 官方维护的微基准测试框架,专为 JVM 环境设计,能规避绝大多数测量陷阱。
✅ 正确做法:用 JMH 替代“单元测试式”的性能测量
JMH 不是单元测试的插件,而是独立的基准测试执行器。它通过 fork 新 JVM 进程、多轮预热、统计采样、反优化保护等方式,把测量精度推到纳秒级。
你需要:
-
不写
@Test,改用@Benchmark - 不手动记时间差,交由 JMH 控制生命周期
- 不共享测试状态而不加控制,必须用
@State明确作用域
? 关键配置要点
-
添加依赖(Maven)
<dependency><groupid>org.openjdk.jmh</groupid><artifactid>jmh-core</artifactid><version>1.36</version><scope>test</scope></dependency><dependency><groupid>org.openjdk.jmh</groupid><artifactid>jmh-generator-annprocess</artifactid><version>1.36</version><scope>test</scope></dependency>
⚠️ IDE 中必须启用 annotation processing,否则
@Benchmark不会生成可执行代码。 -
定义状态与初始化
Java Development Manual下载Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
@State(Scope.Thread) public class CollectionSizeBenchmark { private List<string> list; @Setup(Level.Trial) public void init() { list = new ArrayList(); for (int i = 0; i </string> -
标记待测方法并指定模式
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(1) public class CollectionSizeBenchmark { // ... 上面的状态类内容 @Benchmark public int sizeViaField() { return list.size(); // 直接读字段 } @Benchmark public int sizeViaMethod() { return list.size(); // 实际调用方法(多数实现中就是返回字段) } }
? 运行与解读结果
-
编译后用命令行运行(推荐):
mvn clean install java -jar target/benchmarks.jar -f 1 -wi 5 -i 5
-f 1表示 fork 1 次新进程;-wi 5是 5 轮预热;-i 5是 5 轮正式测量。 -
输出示例:
Benchmark Mode Cnt Score Error Units CollectionSizeBenchmark.sizeViaField avgt 5 2.145 ± 0.042 ns/op CollectionSizeBenchmark.sizeViaMethod avgt 5 2.151 ± 0.038 ns/op
两组结果误差重叠,说明无实质差异;若差距超过 3 倍标准差,才值得深挖。
❌ 别再这样做了(常见误区)
- 在
@Test方法里反复调用目标方法 10000 次并取平均 —— JIT 可能全程没编译,或整个循环被优化掉。 - 把集合初始化写在
@Test方法内 —— 初始化开销混入测量,且每次新建对象影响 GC。 - 用
currentTimeMillis()测毫秒级 —— 分辨率太低,无法反映微操作差异。 - 多个
@Benchmark共享未加同步的可变状态 —— 引发竞争或缓存污染,结果随机波动。
JMH 不是“增强版单元测试”,而是另一套工程实践。它要求你换一种思维:不验证“对不对”,而回答“快多少”“稳不稳定”“是否受干扰”。只要结构写对、配置到位、运行方式规范,结果就可信。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










