一、简介

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();
    }
}

在这里插入图片描述

参考资料

https://zhuanlan.zhihu.com/p/381283590

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐