JMH ( Java MicroBenchmark Harness,JAVA 微基准测试套件 ) , 专门用于代码微基准测试的工具套件, 是基于方法层面的基准测试,精度可以达到微秒级。当定位到热点方法,并进一步优化方法性能的时候,就可以使用JMH对优化的结果进行量化的分析。

  1. [应用场景]
  2. :准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  3. :对比接口不同实现在给定条件下的吞吐量,找到最优实现
  4. :查看多少百分比的请求在多长时间内完成
[基本概念]

    :Mode , JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。

   # Throughput , 整体吞吐量,例如“1秒内可以执行多少次调用”

   # AverageTime, 调用的平均时间,例如“每次调用平均耗时xxx毫秒”

   # SampleTime,  随机取样,最后输出取样结果的分布

   # SingleShotTime,  以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。

     往往同时把 warmup 次数设为0,用于测试冷启动时的性能

    :Iteration , JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒。

    JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间

  :Warmup , 实际进行 benchmark 前先进行预热的行为

    # JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度

[注解]

    :@BenchmarkMode , 类和方法注解,配置Benchmark时所使用的模式

  :@State , 类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期。

    # JMH允许多线程同时执行测试

    -> Scope.Thread , 默认的State,每个测试线程分配一个实例

    -> Scope.Benchmark , 所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能

    -> Scope.Group , 每个线程组共享一个实例

    :@OutputTimeUnit , benchmark结果所使用的时间单位,java.util.concurrent.TimeUnit标准时间单位

  :@Benchmark , 方法注解,表示该方法是需要进行 benchmark 的对象

  :@Setup , 方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化

  :@TearDown , 方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等

  :@Param , 成员注解,可以用来指定某项参数的多种情况

    # 适合用来测试一个函数在不同的参数输入的情况下的性能

一、JMH 环境搭建

<properties>
  <jmh.version>1.14.1</jmh.version>
</properties>

<!-- 压测工具 -->
<dependency>
  <groupId>org.openjdk.jmh</groupId>
  <artifactId>jmh-core</artifactId>
  <version>${jmh.version}</version>
</dependency>
<dependency>
  <groupId>org.openjdk.jmh</groupId>
  <artifactId>jmh-generator-annprocess</artifactId>
  <version>${jmh.version}</version>
  <scope>provided</scope>
</dependency>

<!-- 插件 -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <finalName>microbenchmarks</finalName>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>org.openjdk.jmh.Main</mainClass>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>
// 简单的测试案例

// 测试方法平均执行时间
@BenchmarkMode(Mode.AverageTime)
// 输出结果的时间粒度为微秒
@OutputTimeUnit(TimeUnit.MICROSECONDS)
// 每个测试线程一个实例
@State(Scope.Thread)
public class FirstBenchMark {
    private static Logger log = LoggerFactory.getLogger(FirstBenchMark.class);
    @Benchmark
    public String stringConcat() {
        String a = "a";
        String b = "b";
        String c = "c";
        String s = a + b + c;
        log.debug(s);
        return s;
    }
    public static void main(String[] args) throws RunnerException {
        // 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试
        Options opt = new OptionsBuilder().include(FirstBenchMark.class.getSimpleName()).forks(1).warmupIterations(5)
                .measurementIterations(5).build();
        new Runner(opt).run();
    }
}