JMH是新的microbenchmark(微基准测试)框架(2013年首次发布)。与其他众多框架相比它的特色优势在于,它是由Oracle实现JIT的相同人员开发的。特别是我想提一下Aleksey Shipilev和他优秀的博客文章。JMH可能与最新的Oracle JRE同步,其结果可信度很高。

HelloWorld

学习JMH的第一步当然是完成一个简单的Hello World。如何来完成呢?满足3个条件:

  • 设置JMH-core和jmh-generator-annprocess的maven依赖
  • 使用@Benchmark来注解测试方法
  • 安装JMH插件(IDEA)
1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>

然后写一个测试例子,参考于JMH Samples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class BaselineBenchmarks {
int i;
@Benchmark
public void noop() {
}
@Benchmark
public void increment() {
i++;
}
}

这里来解释代码的意义,首先排除JMH相关的注解:

1
2
3
4
5
6
7
8
9
10
public class BaselineBenchmarks {
int i;
public void noop() {
}
public void increment() {
i++;
}
}

这样就好理解了,BaselineBenchmarks类中有1个int的字段i两个方法noop()increment()其中increment()方法中调用了i++

好了接下来说明JMH的注解,首先是@Benchmark, 这个很好理解代表该注解的方法是一个基准测试方法,你可以想象和单元测试的@Test一样。

@BenchmarkMode注解表示使用特定的测试模式,相关参数见下表:

名称 描述
Mode.Throughput 计算一个时间单位内操作数量
Mode.AverageTime 计算平均运行时间
Mode.SampleTime 计算一个方法的运行时间(包括百分位)
Mode.SingleShotTime 方法仅运行一次(用于冷测试模式)。或者特定批量大小的迭代多次运行(具体查看后面的@Measurement注解)——这种情况下JMH将计算批处理运行时间(一次批处理所有调用的总时间)
这些模式的任意组合 可以指定这些模式的任意组合——该测试运行多次(取决于请求模式的数量)
Mode.All 所有模式依次运行

@OutputTimeUnit是用来指定时间单位,它用一个标准Java类型java.util.concurrent.TimeUnit作为参数。如果在一个测试中指定了多种测试模式,给定的时间单位将用于所有的测试。

@State注解定义了给定类实例的可用范围。JMH可以在多线程同时运行的环境测试,因此需要选择正确的状态。

名称 描述
Scope.Thread 默认状态。实例将分配给运行给定测试的每个线程。
Scope.Benchmark 运行相同测试的所有线程将共享实例。可以用来测试状态对象的多线程性能(或者仅标记该范围的基准)。
Scope.Group 实例分配给每个线程组(查看后面的线程组部分)

参考文档