## JMH是什么
JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。该工具是由 Oracle 内部实现 JIT 的大牛们编写的,他们应该比任何人都了解 JIT 以及 JVM 对于基准测试的影响。
## 应用场景
1. 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
2. 对比接口不同实现在给定条件下的吞吐量
3. 查看多少百分比的请求在多长时间内完成
## 如何使用
1. 引入依赖,jdk9自带,低于9的版本需要自行引入
~~~
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
</dependency>
~~~
2. 对比使用`+`号连接字符串和`StringBuilder.append()`连接字符串的性能
~~~
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectTest {
@Param(value = {"10", "50", "100"})
private int length;
@Benchmark
public void testStringAdd(Blackhole blackhole) {
String a = "";
for (int i = 0; i < length; i++) {
a += i;
}
blackhole.consume(a);
}
@Benchmark
public void testStringBuilderAdd(Blackhole blackhole) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(i);
}
blackhole.consume(sb.toString());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(StringConnectTest.class.getSimpleName())
.result("result.json")
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
}
~~~
3. 运行后得出如下输出,且会保持结果数据到`result.json`中
过程信息如下:(其中一段)
```
# JMH version: 1.23
# VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM, 25.221-b11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=59182:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.mango.demo.mt.jmh.StringConnectTest.testStringBuilderAdd
# Parameters: (length = 100)
# Run progress: 83.33% complete, ETA 00:00:29
# Fork: 1 of 1
# Warmup Iteration 1: 2834.364 ±(99.9%) 1230.495 ns/op
# Warmup Iteration 2: 2350.791 ±(99.9%) 397.253 ns/op
# Warmup Iteration 3: 2150.000 ±(99.9%) 642.696 ns/op
Iteration 1: 2174.173 ±(99.9%) 343.439 ns/op
Iteration 2: 2256.638 ±(99.9%) 85.831 ns/op
Iteration 3: 4466.223 ±(99.9%) 161.050 ns/op
Iteration 4: 2378.494 ±(99.9%) 107.129 ns/op
Iteration 5: 2259.465 ±(99.9%) 398.071 ns/op
Result "org.mango.demo.mt.jmh.StringConnectTest.testStringBuilderAdd":
2706.999 ±(99.9%) 3797.231 ns/op [Average]
(min, avg, max) = (2174.173, 2706.999, 4466.223), stdev = 986.129
CI (99.9%): [≈ 0, 6504.229] (assumes normal distribution)
# Run complete. Total time: 00:02:55
```
结果信息输出如下:
```
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark (length) Mode Cnt Score Error Units
StringConnectTest.testStringAdd 10 avgt 5 466.614 ± 578.636 ns/op
StringConnectTest.testStringAdd 50 avgt 5 3100.612 ± 246.366 ns/op
StringConnectTest.testStringAdd 100 avgt 5 13679.748 ± 10239.498 ns/op
StringConnectTest.testStringBuilderAdd 10 avgt 5 242.057 ± 240.442 ns/op
StringConnectTest.testStringBuilderAdd 50 avgt 5 1376.971 ± 1211.945 ns/op
StringConnectTest.testStringBuilderAdd 100 avgt 5 2706.999 ± 3797.231 ns/op
Benchmark result is saved to result.json
```
## 结果可视化
http://deepoove.com/jmh-visual-chart/:
![](https://img.kancloud.cn/85/fb/85fb329ba2c73643bdb0916a50a55a69_1536x976.png)
https://jmh.morethan.io/:
![](https://img.kancloud.cn/01/c8/01c839f55105d0a4e13f6a68eb487da0_2722x800.png)
## jar包方式部署运行
小型的测试,直接本地运行Main方法测试即可;但是对于一些特定环境或者大型测试,需要放到服务器上跑才行,所以需要jar包方式运行。
使用Maven打包插件得到jar包
~~~
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>jmh-demo</finalName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
~~~
执行如下命令,运行测试:
```
java -jar jmh-demo.jar StringConnectTest
```
## JMH基础
为了能够更好地使用 JMH 的各项功能,下面对 JMH 的基本概念进行讲解:
### @BenchmarkMode
![](https://img.kancloud.cn/40/d7/40d75864e0d4296bdc4968f78537d388_1328x440.png)
### @State
![](https://img.kancloud.cn/b8/84/b8843b30bdb63b0ad85e30dc9dd3abbf_1316x350.png)
### @OutputTimeUnit
为统计结果的时间单位,可用于类或者方法注解
### @Warmup
![](https://img.kancloud.cn/52/f3/52f3443b1ba00c56ef90cd5128a114e1_1336x540.png)
### @Measurement
实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和`@Warmup`相同。
### @Threads
每个进程中的测试线程,可用于类或者方法上。
### @Fork
进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。
### @Param
指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义 @State 注解。
## JMH插件
idea插件:https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness
## JMH陷阱
![](https://img.kancloud.cn/44/65/44653aa6ee3de844314ca024f9d6391d_1352x1116.png)
## 参考
> 为什么要用JMH?何时应该用? - 武培轩的回答 - 知乎 https://www.zhihu.com/question/276455629/answer/1259967560
> 葛一鸣 * 《Java高并发程序设计》
- 面试突击
- Java虚拟机
- 认识字节码
- 000Java发展历史
- 000Macos10.15.7上编译OpenJDK8u
- 001熟悉Java内存区域
- 002熟悉HotSpot中的对象
- 003Java如何计算对象大小
- 004垃圾判定算法与4大引用
- 005回收堆和方法区中对象
- 006垃圾收集算法
- 007HotSpot虚拟机垃圾算法实现篇1
- 007HotSpot虚拟机垃圾算法实现篇2
- 007HotSpot虚拟机垃圾算法实现篇3
- 008垃圾收集器
- 009内存分配与回收策略
- 010Java虚拟机相关工具
- 011调优案例分析
- 012一次IDEA的启动速度调优
- 013类文件Class的结构
- 014熟悉字节码指令
- 015类加载机制(过程)
- 016类加载器
- IDEA的JVM参数
- Java基础
- Java自动装箱与拆箱
- Java基础数据类型
- Java方法的参数传递
- Java并发
- 001走入并行的世界
- 002并行程序基础
- 003熟悉Java内存模型JMM
- 004Java并发之volatile关键字
- 005线程池入门到精通
- 006Java多线程间的同步控制方法
- 007Java维基准测试框架JMH
- 008Java并发容器
- 009Java的线程实现
- 010Java关键字synchronized
- 011一些并行模式的熟悉
- 单例模式和不变模式
- 生产者消费者模式
- Future模式
- 012一些并行算法的熟悉
- 面试总结
- 长亮一面