JMH 微基准测试:精准测量Java代码性能的实践指南

在日常开发中,你是否经常遇到这样的疑问:
两种不同写法的代码,哪种性能更好?
这个小优化到底能带来多少性能提升?
为什么我简单的时间测量结果每次都不一样?
如果你有过这些困惑,那么今天介绍的 JMH(Java Microbenchmark Harness)将是你的得力工具。作为 OpenJDK 官方推荐的基准测试框架,JMH 专门解决“如何准确测量一小段代码性能”的难题。
一、为什么需要 JMH?
在介绍具体用法前,我们先看一个常见的误区:
// 错误示例:手动测量时间
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < 10000; i++) {
// 测试代码
}
long end = System.nanoTime();
System.out.println(“耗时:” + (end – start) + “ns”);
}
这种方法的五大问题:
JVM预热:JVM的JIT编译是动态发生的
死码消除:JVM会优化掉“无用”代码
编译优化:循环可能被展开或优化
噪声干扰:GC、OS调度等随机影响
测量误差:纳秒级测量的精度问题
JMH 正是为了解决这些问题而生的专业工具。
二、快速入门 JMH
2.1 项目搭建(Maven)
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>provided</scope>
</dependency>
2.2 第一个基准测试
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;

@BenchmarkMode(Mode.AverageTime) // 测试模式:平均时间
@OutputTimeUnit(TimeUnit.NANOSECONDS) // 时间单位
@Warmup(iterations = 3, time = 1) // 预热:3轮,每轮1秒
@Measurement(iterations = 5, time = 1) // 测量:5轮,每轮1秒
@Fork(2) // 进程数
@State(Scope.Thread) // 每个线程独享状态
public class FirstBenchmark {

private int x = 42;
private int y = 13;

@Benchmark
public int testAdd() {
return x + y;
}

@Benchmark
public int testMultiply() {
return x * y;
}

public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(FirstBenchmark.class.getSimpleName())
.build();
new Runner(options).run();
}
}
运行结果示例:
Benchmark Mode Cnt Score Error Units
FirstBenchmark.testAdd avgt 10 2.123 ± 0.045 ns/op
FirstBenchmark.testMultiply avgt 10 2.145 ± 0.032 ns/op
三、核心注解详解
3.1 基准模式 (@BenchmarkMode)
Mode.Throughput:吞吐量(单位时间内操作次数)
Mode.AverageTime:平均耗时
Mode.SampleTime:采样时间分布
Mode.SingleShotTime:单次执行时间
Mode.All:所有模式
3.2 状态管理 (@State)
@State(Scope.Thread) // 线程独享
@State(Scope.Benchmark) // 所有线程共享
@State(Scope.Group) // 线程组共享

public class StateBenchmark {
@Param({“10”, “100”, “1000”})
public int size;

private List<String> list;

@Setup
public void setup() {
list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(“item” + i);
}
}

@Benchmark
public int testIteration() {
int sum = 0;
for (String s : list) {
sum += s.length();
}
return sum;
}

@TearDown
public void tearDown() {
list = null;
}
}
3.3 预热与测量
@Warmup(iterations = 3, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
四、实战案例:字符串拼接性能对比
让我们用 JMH 解决一个经典问题:哪种字符串拼接方式性能最好?
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class StringConcatenationBenchmark {

private String str1 = “Hello”;
private String str2 = “World”;
private String str3 = “Java”;
private String str4 = “JMH”;

@Benchmark
public String testPlusOperator() {
return str1 + str2 + str3 + str4;
}

@Benchmark
public String testStringBuilder() {
return new StringBuilder()
.append(str1)
.append(str2)
.append(str3)
.append(str4)
.toString();
}

@Benchmark
public String testStringBuffer() {
return new StringBuffer()
.append(str1)
.append(str2)
.append(str3)
.append(str4)
.toString();
}

@Benchmark
public String testStringFormat() {
return String.format(“%s%s%s%s”, str1, str2, str3, str4);
}

@Benchmark
public String testStringJoin() {
return String.join(“”, str1, str2, str3, str4);
}
}
运行结果分析(示例):
StringConcatenationBenchmark.testPlusOperator thrpt 10 45.678 ± 2.345 ops/s
StringConcatenationBenchmark.testStringBuilder thrpt 10 89.123 ± 3.456 ops/s
StringConcatenationBenchmark.testStringBuffer thrpt 10 23.456 ± 1.234 ops/s
StringConcatenationBenchmark.testStringFormat thrpt 10 5.678 ± 0.345 ops/s
StringConcatenationBenchmark.testStringJoin thrpt 10 67.890 ± 2.678 ops/s
结论:StringBuilder 性能最好,String.format 性能最差。
五、高级特性
5.1 消除死码消除
@Benchmark
public void testAvoidDeadCode(Blackhole blackhole) {
int result = expensiveCalculation();
blackhole.consume(result); // 防止JVM优化掉结果
}
5.2 控制编译器优化
@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE) // 禁止内联
public int testNoInline() {
return compute();
}
5.3 多线程测试
@State(Scope.Group)
@Group(“myGroup”)
@BenchmarkMode(Mode.Throughput)
public class ThreadBenchmark {

private AtomicInteger counter = new AtomicInteger();

@Benchmark
@GroupThreads(4) // 4个生产者线程
public void producer() {
counter.incrementAndGet();
}

@Benchmark
@GroupThreads(4) // 4个消费者线程
public int consumer() {
return counter.get();
}
}
六、最佳实践与避坑指南
6.1 必须遵循的原则
永远不要相信一次运行结果:多次运行,观察稳定性
理解误差范围:关注 ± 后面的误差值
合理设置预热:确保JVM达到稳定状态
使用合适的测量单位:ns、us、ms、s
6.2 常见陷阱
// 错误:可变状态导致结果不可靠
@State(Scope.Thread)
public class BadBenchmark {
private int count = 0;

@Benchmark
public int testIncrement() {
return ++count; // 每次运行结果不同!
}
}

// 正确:每次调用独立
@State(Scope.Thread)
public class GoodBenchmark {

@Benchmark
public int testIncrement(Blackhole blackhole) {
int count = 0;
for (int i = 0; i < 1000; i++) {
count++;
}
blackhole.consume(count);
return count;
}
}
6.3 IDE集成
// 通过注解处理器自动生成
@GenerateMicroBenchmark
public class AutoGenerateBenchmark {
// JMH会自动生成运行代码
}
七、实际项目中的集成
7.1 集成到构建流程
<!– Maven配置示例 –>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>benchmarks</finalName>
<transformers>
<transformer implementation=”org.apache.maven.plugins.shade.resource.ManifestResourceTransformer”>
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
7.2 命令行运行
# 运行所有基准测试
java -jar target/benchmarks.jar

# 运行特定测试
java -jar target/benchmarks.jar StringConcatenationBenchmark

# 指定参数
java -jar target/benchmarks.jar -i 10 -wi 5 -f 3
八、总结
JMH 是一个强大而专业的微基准测试工具,它通过精心设计的机制避免了JVM优化的各种陷阱,为我们提供了可靠的性能测量手段。记住:
不要手动测时间:用专业工具做专业事
结果要可重复:关注误差范围,多次验证
结合场景分析:微基准测试结果需要在实际场景中验证
持续监控:性能测试应该成为持续集成的一部分
最后,性能优化的黄金法则:先测量,再优化,再测量。在没有准确测量之前,所有的”性能优化”都可能是南辕北辙。
资源推荐:
官方示例:https://github.com/openjdk/jmh/tree/master/jmh-samples
用户指南:https://openjdk.org/projects/code-tools/jmh/
实战案例:https://shipilev.net/talks/

购买须知/免责声明
1.本文部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责。
2.若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。
3.如果本站有侵犯、不妥之处的资源,请在网站右边客服联系我们。将会第一时间解决!
4.本站所有内容均由互联网收集整理、网友上传,仅供大家参考、学习,不存在任何商业目的与商业用途。
5.本站提供的所有资源仅供参考学习使用,版权归原著所有,禁止下载本站资源参与商业和非法行为,请在24小时之内自行删除!
6.不保证任何源码框架的完整性。
7.侵权联系邮箱:aliyun6168@gail.com / aliyun666888@gail.com
8.若您最终确认购买,则视为您100%认同并接受以上所述全部内容。

小璐导航资源站 后端编程 JMH 微基准测试:精准测量Java代码性能的实践指南 https://o789.cn/25264.html

下一篇:

已经没有下一篇了!

相关文章

猜你喜欢