JMH基准测试框架编写微基准测试
请详细解释**JMH(Java Microbenchmark Harness)**的使用方法。如何编写正确的微基准测试避免JIT编译优化、死代码消除和常量折叠等问题?JMH的注解(@Benchmark/@State/@Warmup/@Measurement/@BenchmarkMode/@OutputTimeUnit)如何使用?在IntelliJ IDEA中如何运行JMH测试?
回答
我是大山
什么是JMH
JMH是OpenJDK开发的Java微基准测试框架,用于精确测量代码片段的性能(纳秒/微秒级),解决JIT编译、死代码消除、常量折叠等优化导致的测量失真问题。
常见陷阱与JMH解决方案
@BenchmarkMode(Mode.AverageTime) // 平均耗时
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1) // 5轮预热
@Measurement(iterations = 10, time = 1) // 10轮测量
@Fork(1) // 1个Fork进程
@State(Scope.Thread)
public class StringConcatBenchmark {
String a = "Hello";
String b = "World";
@Benchmark
public String concatWithPlus() {
// 黑洞消费返回值,防止死代码消除
return a + " " + b;
}
@Benchmark
public String concatWithBuilder() {
return new StringBuilder(a)
.append(" ")
.append(b)
.toString();
}
// 参数化
@Benchmark
public int sum(@Param("100") int size) { // @Param多组参数
int sum = 0;
for (int i = 0; i < size; i++) sum += i;
return sum;
}
}
关键概念
| 概念 | 说明 |
|---|---|
| @Benchmark | 标记基准测试方法 |
| @State | 状态对象(Scope.Thread/Benchmark/Group) |
| @Warmup | 预热设置,让JIT完成优化 |
| @Measurement | 实际测量设置 |
| @Fork | Fork次数,每个Fork独立JVM进程 |
| @BenchmarkMode | Throughput/AverageTime/SampleTime/SingleShotTime |
| @OutputTimeUnit | 时间单位 |
| Blackhole | 消费返回值,防止死代码消除 |
| @Param | 多组参数测试 |
运行方式
# Maven
mvn clean verify
java -jar target/benchmarks.jar
# IDEA插件:JMH Plugin(自动添加@Benchmark)
最佳实践
- 永远包含预热:JIT编译前5-10轮数据不可信
- 使用Blackhole:返回值和参数都通过Blackhole消费
- 避免常量折叠:从State或参数中获取测试数据
- Fork独立JVM:避免不同测试间的JIT干扰
- 消除噪音:不要在测试中做IO、网络、文件操作