当前位置:首页>java>JAVA:Spring Boot 集成 JMH 进行基准测试

JAVA:Spring Boot 集成 JMH 进行基准测试

  • 2026-09-05 23:51:52
JAVA:Spring Boot 集成 JMH 进行基准测试

点击关注公众号,Java干货及时送达

1、简述

在性能优化中,写高效代码离不开准确的基准测试。而 Java 的 JIT 编译器会对代码进行优化(如方法内联、死代码消除等),导致简单的测试方法可能得不到真实的性能数据。这时候,JMH(Java Microbenchmark Harness)就派上用场了。

JMH 是 Java 官方提供的基准测试框架,由 Oracle 的 HotSpot 团队 开发,专门用来测量 Java 代码的微观性能,避免常见的基准测试误区。

样例代码:https://gitee.com/lhdxhl/springboot-example.git


2、主要特点

JMH是一个用于微基准测试的Java库,它允许开发者对代码的热点进行精确的性能测试。JMH由OpenJDK团队开发,是Java性能测试领域的事实标准。

JMH的主要特点

  • 准确性:避免 JIT 编译、内存屏障等问题导致的测试失真。
  • 多线程支持:可以模拟多线程环境下的性能表现。
  • 灵活性:支持多种参数化配置。
  • 方便集成:可以通过 Maven 或 Gradle 管理。

3、 实践样例

3.1 引入依赖

在 Maven 项目中,添加以下依赖:

<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.38</version></dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.38</version><scope>provided</scope></dependency>

如果使用 Gradle:

dependencies {    implementation 'org.openjdk.jmh:jmh-core:1.38'    annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.38'}

3.2 注解

JMH 提供了注解和配置选项,用于控制基准测试的行为。以下是常用的注解:

  • @Benchmark:标记基准测试方法。
  • @State:定义基准测试的状态,避免每次运行都重新初始化。
  • @Setup 和 @TearDown:在测试运行前后执行初始化或清理逻辑。
  • @BenchmarkMode:指定基准测试的模式(如吞吐量、平均时间)。
  • @OutputTimeUnit:指定测试结果的时间单位。
  • @Fork:指定运行多少个 fork(独立 JVM 测试)。
  • @Warmup:指定预热次数,JVM 在预热阶段会进行 JIT 优化。
  • @Threads:模拟线程数。

3.3 一个简单的 JMH 示例

以下是对比字符串拼接方式的基准测试代码:

package com.lm.jmh.example;import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.concurrent.TimeUnit;@State(Scope.Thread)// 每个线程一个独立的状态@BenchmarkMode(Mode.AverageTime) // 测试平均耗时@OutputTimeUnit(TimeUnit.NANOSECONDS)// 结果单位@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Fork(0)publicclassStringConcatenationBenchmark{privatestaticfinalint ITERATIONS = 10000;@Benchmarkpublic String testStringConcat(){        String result = "";for (int i = 0; i < ITERATIONS; i++) {            result += i; // 使用 + 拼接        }return result;    }@Benchmarkpublic String testStringBuilder(){        StringBuilder sb = new StringBuilder();for (int i = 0; i < ITERATIONS; i++) {            sb.append(i); // 使用 StringBuilder 拼接        }return sb.toString();    }// 主方法,用于运行基准测试publicstaticvoidmain(String[] args)throws RunnerException {        Options opt = new OptionsBuilder()                .include(StringConcatenationBenchmark.class.getSimpleName())                .build();new Runner(opt).run();    }}

通过执行Main方法,示例输出假设测试结果如下:

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up onwhy the numbers are the way they are. Use profilers (see -prof, -lprof), design factorialexperiments, perform baseline and negative tests that provide experimental control, make surethe benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.Do not assume the numbers tell you what you want them to tell.Benchmark                                       Mode  Cnt         Score         Error  UnitsStringConcatenationBenchmark.testStringBuilder  avgt    5    125079.181 ±    2923.749  ns/opStringConcatenationBenchmark.testStringConcat   avgt    5  54042413.695 ± 2083577.967  ns/opDisconnected from the target VM, address: '127.0.0.1:63507', transport: 'socket'

结果表明,StringBuilder 拼接方式的性能远优于直接使用 +。

3.4 多线程测试示例

以下代码测试了一个线程安全计数器与非线程安全计数器的性能对比:

package com.lm.jmh.example;import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;@BenchmarkMode(Mode.Throughput) // 测试吞吐量@OutputTimeUnit(TimeUnit.MILLISECONDS)@State(Scope.Group)@Fork(0)publicclassMultiThreadedBenchmark{private AtomicInteger atomicCounter = new AtomicInteger();privateint normalCounter = 0;@Benchmark@Group("atomic")@GroupThreads(4)publicinttestAtomicIncrement(){return atomicCounter.incrementAndGet();    }@Benchmark@Group("normal")@GroupThreads(4)publicinttestNormalIncrement(){return ++normalCounter; // 非线程安全    }// 主方法,用于运行基准测试publicstaticvoidmain(String[] args)throws RunnerException {        Options opt = new OptionsBuilder()                .include(MultiThreadedBenchmark.class.getSimpleName())                .build();new Runner(opt).run();    }}

通过执行Main方法,示例输出假设测试结果如下:

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up onwhy the numbers are the way they are. Use profilers (see -prof, -lprof), design factorialexperiments, perform baseline and negative tests that provide experimental control, make surethe benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.Do not assume the numbers tell you what you want them to tell.Benchmark                       Mode  Cnt        Score        Error   UnitsMultiThreadedBenchmark.atomic  thrpt    5    49095.263 ±   1045.901  ops/msMultiThreadedBenchmark.normal  thrpt    5  1531294.405 ± 141466.600  ops/msDisconnected from the target VM, address: '127.0.0.1:63578', transport: 'socket'

运行后可以看到,在多线程环境下,AtomicInteger 的性能略低于普通计数器,但能保证线程安全。


4、JMH 的应用场景

  • 比较不同算法的性能:选择最优实现。
  • 验证并发代码性能:如锁与无锁方案的对比。
  • 分析不同库或框架的性能:如 JSON 解析库、数据库驱动。
  • 优化热点代码:识别性能瓶颈。

常见注意事项:

  • 避免编译器优化:JMH 会自动生成代码,避免 JIT 对测试代码的优化。
  • 预热阶段的重要性:默认 JMH 会进行多次预热,以确保 JIT 已完全优化。
  • 隔离外部因素:运行测试时,关闭无关进程,避免影响结果。

5、总结

通过本文的介绍,相信你已经掌握了 JMH 的基本用法和实践示例。如果你对 Java 性能调优感兴趣,不妨尝试将 JMH 应用于自己的项目中,量化性能并验证优化效果!

往
期
推
荐

1、SpringBoot 集成 Hera,让日志查看从 “找罪证” 变 “查答案”!

2、从4.2秒到460毫秒:我用CompletableFuture让商品详情页“飞”起来了!

3、MyBatis-Plus 开发提速器:mybatis-plus-generator-ui

4、美团一面:Spring Cloud 远程调用为啥要采用 HTTP,而不是 RPC?

5、相比高人气的Rust、Go,为何 Java、C 在工具层面进展缓慢?

6、Spring Cloud砍掉3层POM依赖,再见被抛弃的spring-cloud-starter-parent

点分享

点收藏

点点赞

点在看

最新文章

随机文章