概述
JMH 的全名是 Java Microbenchmark Harness,它是由 Java 虚拟机团队开发的一款用于 Java 微基准测试工具。
依赖库
https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.35</version></dependency><!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.35</version><scope>provided</scope></dependency>
出现Exception in thread “main” java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList问题,
问题就在于:jmh-generator-annprocess依赖的scope配置为test,此时表示该依赖仅仅在测试阶段会参与进来,包括测试代码的编译,执行。
由于这个依赖并不用于生产,我们可以将scope设置为provided
JMH注解
@BenchmarkMode
JMH使用@BenchmarkMode这个注解来声明运行模式,BenchmarkMode既可以在class上进行注解设置,也可以在基准方法上进行注解设置,方法中设置的模式将会覆盖class注解上的设置,同样,在Options中也可以进行设置,它将会覆盖所有基准方法上的设置。JMH为我们提供了四种运行模式:
| AverageTime(平均响应时间) | 要用于输出基准测试方法每调用一次所耗费的时间,也就是elapsed time/operation | 
| Throughput(方法吞吐量) | 则刚好与AverageTime相反,它的输出信息表明了在单位时间内可以对该方法调用多少次 | 
| SampleTime(时间采样) | 采用一种抽样的方式来统计基准测试方法的性能结果,与我们常见的直方图几乎是一样的,它会收集所有的性能数据,并且将其分布在不同的区间中。 | 
| SingleShotTime | 主要可用来进行冷测试,不论是Warmup还是Measurement,在每一个批次中基准测试方法只会被执行一次,一般情况下,我们会将Warmup的批次设置为0 | 
@Benchmark
JMH对基准测试的方法需要使用@Benchmark注解进行标记,否则方法将被视为普通方法,并且不会对其执行基准测试
@Warmup
Warmup所做的就是在基准测试代码正式度量之前,先对其进行预热,使得代码的执行是经历过了类的早期优化、JVM运行期编译、JIT优化之后的最终状态,从而能够获得代码真实的性能数据。
参数说明
- timeUnit:时间的单位,默认的单位是秒;
 - iterations:预热阶段的迭代数;
 - time:每次预热的时间;
 - batchSize:批处理大小,指定了每次操作调用几次方法。 ```java @Warmup(
 
iterations = 5,
time = 1,
timeUnit = TimeUnit.SECONDS)
代码预热总计 5 秒(迭代 5 次,每次一秒)。预热过程的测试数据,是不记录测量结果的。<a name="sUeKq"></a>### @MeasurementMeasurement则是真正的度量操作,在每一轮的度量中,所有的度量数据会被纳入统计之中(预热数据不会纳入统计之中)```java@Measurement(iterations = 5,time = 1,timeUnit = TimeUnit.SECONDS)
fork
fork 的值一般设置成 1,表示只使用一个进程进行测试;如果这个数字大于 1,表示会启用新的进程进行测试;但如果设置成 0,程序依然会运行,此时在用户的 JVM 进程上运行
Threads
fork 是面向进程的,而 Threads 是面向线程的。指定了这个注解以后,将会开启并行测试。如果配置了 Threads.MAX,则使用和处理机器核数相同的线程数。
@Setup
@Setup会在每一个基准测试方法执行前被调用,通常用于资源的初始化
@TearDown
@TearDown则会在基准测试方法被执行之后被调用,通常可用于资源的回收清理工作
@BenchmarkMode(Mode.AverageTime)@State(Scope.Thread)@Fork(1)@OutputTimeUnit(TimeUnit.MILLISECONDS)@Warmup(iterations = 3)@Measurement(iterations = 5)public class StringBuildTest {String string = "";StringBuilder stringBuilder = new StringBuilder();@Benchmarkpublic String stringAdd() {for (int i = 0; i < 1000; i++) {string = string + i;}return string;}@Benchmarkpublic String stringBuilderAppend() {for (int i = 0; i < 1000; i++) {stringBuilder.append(i);}return stringBuilder.toString();}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(StringBuildTest.class.getSimpleName()).build();new Runner(opt).run();}}
连接池测试
使用连接池和不使用连接池,它们之间的性能差距到底有多大呢?下面是一个简单的 JMH 测试例子(见仓库),进行一个简单的 set 操作,为 redis 的 key 设置一个随机值。
@Fork(2)@State(Scope.Benchmark)@Warmup(iterations = 5, time = 1)@Measurement(iterations = 5, time = 1)@BenchmarkMode(Mode.Throughput)public class JedisPoolVSJedisBenchmark {JedisPool pool = new JedisPool("localhost", 6379);@Benchmarkpublic void testPool() {Jedis jedis = pool.getResource();jedis.set("a", UUID.randomUUID().toString());jedis.close();}@Benchmarkpublic void testJedis() {Jedis jedis = new Jedis("localhost", 6379);jedis.set("a", UUID.randomUUID().toString());jedis.close();}...
图形化
Options opt = new OptionsBuilder().resultFormat(ResultFormatType.JSON).build();
JMH 支持 5 种格式结果
- TEXT 导出文本文件。
 - CSV 导出 csv 格式文件。
 - SCSV 导出 scsv 等格式的文件。
 - JSON 导出成 json 文件。
 - LATEX 导出到 latex,一种基于 ΤΕΧ 的排版系统。
图形工具
JMH Visualizer
https://jmh.morethan.io/
通过导出 json 文件,上传至 JMH Visualizer 
JMH Visual Chart
http://deepoove.com/jmh-visual-chart/
meta-chart
https://www.meta-chart.com/
