JMH比较典型的应用场景有:

想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
对比接口不同实现在给定条件下的吞吐量,找到最优实现
查看多少百分比的请求在多长时间内完成1 JMH入门案例

Maven依赖

JMH是 被作为JDK9而自带的,但是我们可以通过导入相关依赖或者jar包来使用。

1
2
3
4
5
6
7
8
9
10
11
12
<!--JMH-->
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
</dependency>

程序编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class HelloJMH {
/**
* 字符串拼接StringBuilder基准测试
*/
@Benchmark
public void testStringBuilder() {
StringBuilder str = new StringBuilder();
for (int i = 0; i < 1000; i++) {
str.append(i);
}
String s = str.toString();
}
/**
* 字符串拼接直接相加基准测试
*/
@Benchmark
public void testStringAdd() {
String str = "";
for (int i = 0; i < 1000; i++) {
str = str + i;
}
}

public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(HelloJMH.class.getSimpleName()) //包含的方法
.forks(1) //分出几个进程单独测试
.build();
new Runner(options).run();
}
}

  @Benchmark注解表示该方法是需要进行benchmark测试的方法。
  @BenchmarkMode表示JMH测量方式和角度,本次是测量平均时间。
  @OutputTimeUnit表示benchmark 结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。
  在 Main 方法中,通过Runner 类去运行Options 实例即可。官方提供了一个OptionsBuilder对象去流式构建。OptionsBuilder的其他配置信息在下面讲。

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
1)	# JMH version: 1.21
2) # VM version: JDK 1.8.0_144, Java HotSpot(TM) 64-Bit Server VM, 25.144-b01
3) # VM invoker: C:\Program Files\Java\jdk1.8.0_144\jre\bin\java.exe
4) # VM options: -javaagent:D:\soft\IntelliJ IDEA 2019.3\lib\idea_rt.jar=61956:D:\soft\IntelliJ IDEA 2019.3\bin -Dfile.encoding=UTF-8
5) # Warmup: 5 iterations, 10 s each
6) # Measurement: 5 iterations, 10 s each
7) # Timeout: 10 min per iteration
8) # Threads: 1 thread, will synchronize iterations
9) # Benchmark mode: Average time, time/op
10) # Benchmark: com.thread.test.JMH.HelloJMH.testStringAdd

11) # Run progress: 0.00% complete, ETA 00:03:20
12) # Fork: 1 of 1
13) # Warmup Iteration 1: 506360.123 ns/op
14) # Warmup Iteration 2: 460295.578 ns/op
15) # Warmup Iteration 3: 492550.630 ns/op
16) # Warmup Iteration 4: 482141.558 ns/op
17) # Warmup Iteration 5: 469897.660 ns/op
18) Iteration 1: 443427.726 ns/op
19) Iteration 2: 456970.538 ns/op
20) Iteration 3: 440686.491 ns/op
21) Iteration 4: 451894.998 ns/op
22) Iteration 5: 432889.165 ns/op


23) Result "com.thread.test.JMH.HelloJMH.testStringAdd":
a) 445173.784 ±(99.9%) 36450.901 ns/op [Average]
b) (min, avg, max) = (432889.165, 445173.784, 456970.538), stdev = 9466.183
c) CI (99.9%): [408722.883, 481624.685] (assumes normal distribution)


24) # JMH version: 1.21
25) # VM version: JDK 1.8.0_144, Java HotSpot(TM) 64-Bit Server VM, 25.144-b01
26) # VM invoker: C:\Program Files\Java\jdk1.8.0_144\jre\bin\java.exe
27) # VM options: -javaagent:D:\soft\IntelliJ IDEA 2019.3\lib\idea_rt.jar=61956:D:\soft\IntelliJ IDEA 2019.3\bin -Dfile.encoding=UTF-8
28) # Warmup: 5 iterations, 10 s each //预热次数
29) # Measurement: 5 iterations, 10 s each //度量次数
30) # Timeout: 10 min per iteration
31) # Threads: 1 thread, will synchronize iterations
32) # Benchmark mode: Average time, time/op
33) # Benchmark: com.thread.test.JMH.HelloJMH.testStringBuilder

34) # Run progress: 50.00% complete, ETA 00:01:40
35) # Fork: 1 of 1
36) # Warmup Iteration 1: 10372.126 ns/op
37) # Warmup Iteration 2: 10301.755 ns/op
38) # Warmup Iteration 3: 10006.275 ns/op
39) # Warmup Iteration 4: 9778.343 ns/op
40) # Warmup Iteration 5: 9868.092 ns/op
41) Iteration 1: 9641.269 ns/op
42) Iteration 2: 10259.971 ns/op
43) Iteration 3: 9844.944 ns/op
44) Iteration 4: 9704.533 ns/op
45) Iteration 5: 9711.980 ns/op


46) Result "com.thread.test.JMH.HelloJMH.testStringBuilder":
a) 9832.539 ±(99.9%) 963.347 ns/op [Average]
b) (min, avg, max) = (9641.269, 9832.539, 10259.971), stdev = 250.178
c) CI (99.9%): [8869.193, 10795.886] (assumes normal distribution)


47) # Run complete. Total time: 00:03:21

48) REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
49) why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
50) experiments, perform baseline and negative tests that provide experimental control, make sure
51) the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
52) Do not assume the numbers tell you what you want them to tell.

53) Benchmark Mode Cnt Score Error Units
54) JMH.HelloJMH.testStringAdd avgt 5 445173.784 ± 36450.901 ns/op
55) JMH.HelloJMH.testStringBuilder avgt 5 9832.539 ± 963.347 ns/op

解释:

  • 第1-10行表示测试的基本信息,比如,使用的Java路径,预热代码的迭代次数,测量代码的迭代次数,使用的线程数量,测试的统计单位等。
  • 从第13行开始显示了每次预热迭代的结果,预热迭代不会作为最终的统计结果。预热的目的是让Java虚拟机对被测代码进行足够多的优化,比如,在预热后被测代码应该得到了充分的JIT编译和优化。
  • 从第18行开始显示每次基准测试迭代的结果,每一次迭代都显示了当前的执行速率,即一个操作所花费的时间。
  • 在进行5次迭代后,进行统计,结果在Result后。Result第一段结果告诉了我们最大值、最小值、平均值的信息。第二段是最主要的信息。在本例中,第54、55行显示了testStringBuilder和testStringAdd函数的平均执行花费时间和误差时间。从结果可以看出,大量字符串拼接式时,使用StringBuilder效率更高。
Benchmark Mode Cnt Score Error Units
基准测试执行的方法 模式模式 运行多少次 分数 错误 单位

JMH的基本概念和配置

详细配置请参考原文