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

在性能优化中,写高效代码离不开准确的基准测试。而 Java 的 JIT 编译器会对代码进行优化(如方法内联、死代码消除等),导致简单的测试方法可能得不到真实的性能数据。这时候,JMH(Java Microbenchmark Harness)就派上用场了。
JMH 是 Java 官方提供的基准测试框架,由 Oracle 的 HotSpot 团队 开发,专门用来测量 Java 代码的微观性能,避免常见的基准测试误区。
样例代码:https://gitee.com/lhdxhl/springboot-example.git

JMH是一个用于微基准测试的Java库,它允许开发者对代码的热点进行精确的性能测试。JMH由OpenJDK团队开发,是Java性能测试领域的事实标准。
JMH的主要特点
在 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'}JMH 提供了注解和配置选项,用于控制基准测试的行为。以下是常用的注解:
@Benchmark:标记基准测试方法。@State:定义基准测试的状态,避免每次运行都重新初始化。@Setup 和 @TearDown:在测试运行前后执行初始化或清理逻辑。@BenchmarkMode:指定基准测试的模式(如吞吐量、平均时间)。@OutputTimeUnit:指定测试结果的时间单位。@Fork:指定运行多少个 fork(独立 JVM 测试)。@Warmup:指定预热次数,JVM 在预热阶段会进行 JIT 优化。@Threads:模拟线程数。以下是对比字符串拼接方式的基准测试代码:
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 拼接方式的性能远优于直接使用 +。
以下代码测试了一个线程安全计数器与非线程安全计数器的性能对比:
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 的性能略低于普通计数器,但能保证线程安全。
常见注意事项:
通过本文的介绍,相信你已经掌握了 JMH 的基本用法和实践示例。如果你对 Java 性能调优感兴趣,不妨尝试将 JMH 应用于自己的项目中,量化性能并验证优化效果!

往 期 推 荐
1、SpringBoot 集成 Hera,让日志查看从 “找罪证” 变 “查答案”!
2、从4.2秒到460毫秒:我用CompletableFuture让商品详情页“飞”起来了!
3、MyBatis-Plus 开发提速器:mybatis-plus-generator-ui
4、美团一面:Spring Cloud 远程调用为啥要采用 HTTP,而不是 RPC?
点分享
点收藏
点点赞
点在看