JMH:java官方推出的java基准测试神器
·
文章目录
一、简介
JMH(Java Microbenchmark Harness) 是 OpenJDK 官方出品的Java 微基准测试框架,专门测量纳秒~毫秒级短方法、代码片段性能,彻底解决手写计时(System.nanoTime)所有 JVM 陷阱。
官方示例:
https://hg.openjdk.org/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
1、传统手写基准致命问题
死代码消除 DCE:JIT 发现计算结果未使用,直接删掉整段逻辑,数据完全失真
JIT 编译干扰:冷代码解释执行、热代码 C1/C2 优化、内联、循环展开,冷热性能天差地别
预热不足:未等 JIT 稳定就测量,结果偏低 / 波动极大
GC 噪声:频繁创建对象触发 GC,混入额外耗时
操作系统调度、CPU 调频、线程切换导致结果方差极大
/**
这样的测试结果真的准确吗?答案是否定的,它有下面几个问题:
1、时间精度问题,本身获取到的时间戳就是存在误差的,它和操作系统有关。
2、JVM 在运行时会进行代码预热,说白了就是越跑越快。因为类需要装载、需要准备操作。
3、JVM 会在各个阶段都有可能对你的代码进行优化处理。
4、资源回收的不确定性,可能运行很快,回收很慢。
*/
long start = System.currentTimeMillis();
// ....
long end = System.currentTimeMillis();
System.out.println(end - start);
2、JMH 核心价值:
自动进程隔离、预热 JIT、防死码、多轮迭代统计、过滤 GC 噪声,输出可复现、置信区间可靠的性能指标。
3、依赖包
<!--jmh 基准测试 -->
<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>
二、官方sample解读
1、HelloWorld
import org.openjdk.jmh.annotations.Benchmark;
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;
public class JMHSample_01_HelloWorld {
/**
* 使用 @Benchmark 来标记需要基准测试的方法
*/
@Benchmark
public void wellHelloThere() {
// 方法故意为空
}
/**
* 启动基准测试
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_01_HelloWorld.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
以下是执行结果,可以翻译一下,大概是每秒平均执行20多亿次该方法:
# JMH version: 1.37
# VM version: JDK 1.8.0_452, OpenJDK 64-Bit Server VM, 25.452-b09
# VM invoker: C:\Program Files\Eclipse Adoptium\jdk-8.0.452.9-hotspot\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2025.3.1\lib\idea_rt.jar=63902 -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.demo.springbootdemo.test.JMHSample_01_HelloWorld.wellHelloThere
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration 1: 3265927137.012 ops/s
# Warmup Iteration 2: 2174262707.336 ops/s
# Warmup Iteration 3: 2149069977.000 ops/s
# Warmup Iteration 4: 2173889524.928 ops/s
# Warmup Iteration 5: 2305925623.503 ops/s
Iteration 1: 2151649987.171 ops/s
Iteration 2: 2064556932.243 ops/s
Iteration 3: 2164359863.057 ops/s
Iteration 4: 2014923478.974 ops/s
Iteration 5: 2225449927.657 ops/s
Result "com.demo.springbootdemo.test.JMHSample_01_HelloWorld.wellHelloThere":
2124188037.820 ±(99.9%) 322837612.138 ops/s [Average]
(min, avg, max) = (2014923478.974, 2124188037.820, 2225449927.657), stdev = 83839902.658
CI (99.9%): [1801350425.682, 2447025649.959] (assumes normal distribution)
# Run complete. Total time: 00:01:40
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the 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 Units
JMHSample_01_HelloWorld.wellHelloThere thrpt 5 2124188037.820 ± 322837612.138 ops/s
2、BenchmarkMode
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
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;
public class JMHSample_02_BenchmarkModes {
/*
* Mode.Throughput:在有时间限制的迭代中连续调用基准方法,以及计算我们执行该方法的次数。
*/
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public void measureThroughput() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.AverageTime 测量平均执行时间
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureAvgTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.SampleTime 对执行时间进行采样。
* 这使我们能够推断分布、百分位数等。
*/
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureSamples() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.SingleShotTime 测量单个方法调用时间
*
* 当您需要进行冷启动测试时,此模式非常有用
*/
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureSingleShot() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* 我们还可以同时要求多个基准模式
*/
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureMultiple() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Or even...
*/
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureAll() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* 执行基准测试
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_02_BenchmarkModes.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
3、略
三、示例
1、对比 String+、StringBuilder、StringBuffer 在1000 次拼接下的吞吐量与平均耗时
package com.example.jmh;
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.Throughput, Mode.AverageTime}) // 同时测吞吐量与平均时间
@OutputTimeUnit(TimeUnit.NANOSECONDS) // 输出单位:纳秒
@Warmup(iterations = 3, time = 1) // 预热3轮,每轮1秒
@Measurement(iterations = 5, time = 1) // 测量5轮,每轮1秒
@Fork(2) // fork 2个进程,结果取平均
@Threads(1) // 单线程测试
public class StringConcatBenchmark {
// 被测方法1:String +
@Benchmark
public String stringPlus() {
String s = "";
for (int i = 0; i < 1000; i++) {
s += "a";
}
return s;
}
// 被测方法2:StringBuilder
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("a");
}
return sb.toString();
}
// 被测方法3:StringBuffer
@Benchmark
public String stringBuffer() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 1000; i++) {
sb.append("a");
}
return sb.toString();
}
// 运行入口
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(StringConcatBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}

参考资料
更多推荐


所有评论(0)