JMH是OpenJDK的JIT团队开发的微基准测试框架,可针对基准方法在吞吐量、响应时间等维度进行纳秒/微秒/毫秒/秒级的性能基准测试。随着虚拟机的逐步优化,过去的一些常见说辞已经不再那么绝对,比如:final 修饰的变量性能更好,对象使用后赋null可以加快GC回收等。因此性能的好坏需要量化比较,JMH正是解决这方面的好工具。
JMH典型用法:
1.生成一个maven工程
mvn archetype:generate
-DinteractiveMode=false
-DarchetypeGroupId=org.openjdk.jmh
-DarchetypeArtifactId=jmh-java-benchmark-archetype
-DgroupId=com.jmh
-DartifactId=jmh
-Dversion=1.0.0-SNAPSHOT
执行上述命令后,会创建一个默认的JMH Maven工程。
2.写压测代码
默认生成的 MyBenchmark.java 就是执行JMH的压测类,方法 testMethod 中就是要压测的代码
代码示例:
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
@GenerateMicroBenchmark
public List<Integer> testMethod() {
//....
}
}
3.构建打包
mvn clean package
4.执行基准测试
打包成功后在target目录下生成了一个JAR文件:microbenchmarks.jar,需要注意的是,官网的运行命令是java -jar target/benchmarks.jar,至于到底是benchmarks.jar还是microbenchmarks.jar,取决于你的POM文件:
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
执行jar包运行命令
java -jar target/microbenchmarks.jar
输出结果如下:
# Run progress: 0.00% complete, ETA 00:00:10
# VM invoker: C:\Program Files\Java\jre1.8.0_181\bin\java.exe
# VM options: <none>
# Fork: 1 of 1
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.afei.jmh.MyBenchmark.testMethod
# Warmup Iteration 1: 1133.738 ns/op
# Warmup Iteration 2: 1169.750 ns/op
# Warmup Iteration 3: 1066.204 ns/op
# Warmup Iteration 4: 1086.300 ns/op
# Warmup Iteration 5: 1145.228 ns/op
Iteration 1: 1045.157 ns/op
Iteration 2: 1064.303 ns/op
Iteration 3: 1064.227 ns/op
Iteration 4: 1053.979 ns/op
Iteration 5: 1055.718 ns/op
Result : 1056.677 ±(99.9%) 30.809 ns/op
Statistics: (min, avg, max) = (1045.157, 1056.677, 1064.303), stdev = 8.001
Confidence interval (99.9%): [1025.868, 1087.486]
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod avgt 5 1056.677 30.809 ns/op
5.结果解读
下面对输出结果一些重要信息进行解读:
@Warmup
由于注解:@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)。所以,基准测试后对代码预热总计5秒(迭代5次,每次1秒)。预热对于压测来说非常非常重要,如果没有预热过程,压测结果会很不准确。这个注解对应的日志如下:
# Warmup Iteration 1: 1133.738 ns/op
# Warmup Iteration 2: 1169.750 ns/op
# Warmup Iteration 3: 1066.204 ns/op
# Warmup Iteration 4: 1086.300 ns/op
# Warmup Iteration 5: 1145.228 ns/op
@Measurement
另外一个重要的注解:@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS),表示循环运行5次,总计5秒时间。
@Fork
这个注解表示fork多少个线程运行基准测试,如果@Fork(1),那么就是一个线程,这时候就是同步模式。
@BenchmarkMode&@OutputTimeUnit
基准测试模式申明为:@BenchmarkMode(Mode.AverageTime)搭配@OutputTimeUnit(TimeUnit.NANOSECONDS)(可选基准测试模式通过枚举Mode得到),笔者的示例是AverageTime,即表示每次操作需要的平均时间,而OutputTimeUnit申明为纳秒,所以基准测试单位是ns/op,即每次操作的纳秒单位平均时间。基准测试结果如下:
Result : 1056.677 ±(99.9%) 30.809 ns/op
Statistics: (min, avg, max) = (1045.157, 1056.677, 1064.303), stdev = 8.001
Confidence interval (99.9%): [1025.868, 1087.486]
最后一段结果如下,重点关注Mean和Units两个字段,组合起来就是1227.928ns/op,即每次操作耗时1056.677纳秒:
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod avgt 5 1056.677 30.809 ns/op
如果我们将@BenchmarkMode(Mode.AverageTime)与@OutputTimeUnit(TimeUnit.NANOSECONDS)的组合,改成@BenchmarkMode(Mode.Throughput)和@OutputTimeUnit(TimeUnit.MILLISECONDS),那么基准测试结果就是每毫秒的吞吐量(即每毫秒多少次操作),结果如下,表示943.437ops/ms:
Benchmark Mode Samples Mean Mean error Units
c.a.j.MyBenchmark.testMethod thrpt 5 943.437 44.060 ops/ms
注:Mean error表示误差,或者波动,与Result的±值对应:Result : 1056.677 ±(99.9%) 30.809 ns/op;
JMH和jMeter的不同
JMH和jMeter的使用场景还是有很大的不同的,jMeter更多的是对rest api进行压测,而JMH关注的粒度更细,它更多的是发现某块性能槽点代码,然后对优化方案进行基准测试对比。