CodeWalk

JMH基准测试框架编写微基准测试

作者:我是大山 · 2026-05-30 12:55

请详细解释**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实际测量设置
@ForkFork次数,每个Fork独立JVM进程
@BenchmarkModeThroughput/AverageTime/SampleTime/SingleShotTime
@OutputTimeUnit时间单位
Blackhole消费返回值,防止死代码消除
@Param多组参数测试

运行方式

# Maven
mvn clean verify
java -jar target/benchmarks.jar

# IDEA插件:JMH Plugin(自动添加@Benchmark)

最佳实践

  1. 永远包含预热:JIT编译前5-10轮数据不可信
  2. 使用Blackhole:返回值和参数都通过Blackhole消费
  3. 避免常量折叠:从State或参数中获取测试数据
  4. Fork独立JVM:避免不同测试间的JIT干扰
  5. 消除噪音:不要在测试中做IO、网络、文件操作