JMH 是什么?
JMH(Java Microbenchmark Harness)是一款专用于代码微基准测试的工具集,其主要聚焦于方法层面的基准测试,精度可达纳秒级别。此工具由 Oracle 内部负责实现 JIT 的杰出人士编写,他们对 JIT 及 JVM 在基准测试方面的影响有着深刻的理解。JMH 不仅能够对 Java 语言进行基准测试,还能对运行在 JVM 上的其他语言开展基准测试。
当热点方法被确定,且希望进一步提升方法性能时,可借助 JMH 对优化后的结果进行量化分析。
JMH 具有一些典型的应用场景,如:
- 精准了解某个方法的执行时间,以及执行时间与输入的相关性;
- 对比接口不同实现方式在特定条件下的吞吐量;
- 查看在特定时间段内完成的请求所占比例等。
JMH 在JDK9 中是自带的 在JDK9 之前 我们需要主动的引入下maven包
<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>
让我们来些第一个 JMH 测试代码吧!
package cn.ideamake.im.auth.service;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import com.google.common.collect.Lists;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author Barcke
* @version 1.0
* @projectName im-auth
* @className Test
* @date 2024/4/30 16:30
* @slogan: 源于生活 高于生活
* @description:
**/
// 用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行 比如 @BenchmarkMode({Mode.AverageTime, Mode.SampleTime})
@BenchmarkMode(
// 每一次方法执行用的平均时间,每次操作的平均时间,单位为 time/op
// Mode.AverageTime
// 随机取样,最后输出取样结果的分布
// Mode.SampleTime
// 只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
// Mode.SingleShotTime
// 吞吐量,每秒执行了多少次调用,单位为 ops/time
Mode.Throughput
// 上面的所有模式都执行一次
// Mode.All
)
// 预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。
@Warmup(
// 预热次数
iterations = 3
// 每次预热的时间
, time = 1
// 时间单位,默认秒
, timeUnit = TimeUnit.SECONDS
// 批处理大小,每次操作调用几次方法
, batchSize = 100
)
@Measurement(iterations = 5, time = 5)
// 每个测试线程数量,可用于类或者方法上。
@Threads(5)
// 进行 fork 的次数,可用于类或者方法上。如果 fork 数是 3 的话,则 JMH 会 fork 出三个进程来进行测试。
@Fork(3)
// 指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试
@State(
// 默认是 Scope.Benchmark 所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
// value = Scope.Benchmark
// 线程作用域,每个线程都有自己的实例,每个线程的实例相互独立,不会相互影响
value = Scope.Thread
// 线程组作用域,线程组内的线程共享一个实例,线程组外的线程不会共享实例,每个线程的实例相互独立,不会相互影响
// value = Scope.Group
)
// 结果输出时间单位 可用于类或者方法注解
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Test {
private final String KEY = "barcke";
private List<String> arrayList;
private List<String> linkedList;
// 测试参数
@Param(value = {"10", "20", "30"})
private int length;
// @Setup 测试初始化方法 对应在执行测试方法之后调用的可以使用注解 @TearDown 声明
@Setup(
// 由于我们设置Warmup和Measurement的不同,因此每一个基准测试方法都会被执行若干个批次,如果想要在每一个基准测试批次执行的前后调用方法,则可以将Level设置为Iteration。
// Level.Iteration
// 意味着在每一个批次的度量过程中,每一次对基准方法的调用前都会执行套件方法。
// Level.Invocation
// Setup和TearDown默认的配置,该套件方法会在每一个基准测试方法的所有批次执行的前后被执行。
Level.Trial
)
public void setup() {
arrayList = Lists.newCopyOnWriteArrayList();
linkedList = new LinkedList<>();
}
// Benchmark 用于 测试方法声明
@Benchmark
public List<String> arrayListAdd() {
for (int i = 0; i < length; i++) {
arrayList.add(KEY);
}
return arrayList;
}
// Benchmark 用于 测试方法声明
@Benchmark
public List<String> linkedListAdd() {
for (int i = 0; i < length; i++) {
linkedList.add(KEY);
}
return linkedList;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
// 要导入的测试类
.include(Test.class.getSimpleName())
// 输出测试结果的文件
.result("result.json")
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
}
需要进行测试的方法会用@Benchmark 注解来标识,关于这些注解的详细含义,将在后续内容中进行具体阐述。
在 main()函数中,首先会对测试用例进行相应的配置。通过采用 Builder 模式来配置测试,将配置的各项参数存入 Options 对象中,然后再利用该 Options 对象来构建 Runner 并启动测试。
使用Main方法执行测试
需注意!!!
idea执行基准测试过程中可能会出现内存泄露的报错:java.lang.OutOfMemoryError: Java heap space
加大JVM的内存参数值即可
如:
做好准备工作后,开始运行代码,静待片刻,测试结果就会出炉,下面对结果做一个简单的分析:
最终结果:
可视化 JMH 工具
JMH Visual Chart:http://deepoove.com/jmh-visual-chart/
JMH Visualizer:https://jmh.morethan.io/
将json文件导入到网站中则可以得到可视化图表数据
我跑出来的json数据 不过吞吐量没有啥区别~
[
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.arrayListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "10"
},
"primaryMetric" : {
"score" : 2.4948150613578957E-6,
"scoreError" : 1.1238982246115864E-6,
"scoreConfidence" : [
1.3709168367463092E-6,
3.618713285969482E-6
],
"scorePercentiles" : {
"0.0" : 1.4561640572531666E-6,
"50.0" : 2.1912588467079646E-6,
"90.0" : 4.420901428147037E-6,
"95.0" : 4.841074442897258E-6,
"99.0" : 4.841074442897258E-6,
"99.9" : 4.841074442897258E-6,
"99.99" : 4.841074442897258E-6,
"99.999" : 4.841074442897258E-6,
"99.9999" : 4.841074442897258E-6,
"100.0" : 4.841074442897258E-6
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
4.1138049369997915E-6,
2.4765216035616155E-6,
2.1912588467079646E-6,
1.789423605929749E-6,
1.4561640572531666E-6
],
[
4.140786084980223E-6,
2.5591632462186068E-6,
2.2445508618694107E-6,
1.7726205421615725E-6,
1.5585637200319034E-6
],
[
4.841074442897258E-6,
2.800833094186311E-6,
1.9730874497911443E-6,
1.8848866640894546E-6,
1.6194867636902629E-6
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.arrayListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "20"
},
"primaryMetric" : {
"score" : 1.2859270068428564E-6,
"scoreError" : 6.255401258543347E-7,
"scoreConfidence" : [
6.603868809885217E-7,
1.911467132697191E-6
],
"scorePercentiles" : {
"0.0" : 7.927250243370195E-7,
"50.0" : 1.0489617370260857E-6,
"90.0" : 2.4044188117910495E-6,
"95.0" : 2.506172746185885E-6,
"99.0" : 2.506172746185885E-6,
"99.9" : 2.506172746185885E-6,
"99.99" : 2.506172746185885E-6,
"99.999" : 2.506172746185885E-6,
"99.9999" : 2.506172746185885E-6,
"100.0" : 2.506172746185885E-6
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
2.506172746185885E-6,
1.3749914022210378E-6,
1.042460182653776E-6,
8.085105009167249E-7,
7.98261178607378E-7
],
[
2.150161901013991E-6,
1.362837559664351E-6,
1.0489617370260857E-6,
8.930920686728167E-7,
7.940245030098016E-7
],
[
2.336582855527826E-6,
1.4214980159279697E-6,
1.0585275386698327E-6,
9.000978882083538E-7,
7.927250243370195E-7
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.arrayListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "30"
},
"primaryMetric" : {
"score" : 8.471727655992797E-7,
"scoreError" : 3.7052359443423565E-7,
"scoreConfidence" : [
4.766491711650441E-7,
1.2176963600335153E-6
],
"scorePercentiles" : {
"0.0" : 5.302584706779764E-7,
"50.0" : 6.834675315679355E-7,
"90.0" : 1.52180707672635E-6,
"95.0" : 1.5528636107639667E-6,
"99.0" : 1.5528636107639667E-6,
"99.9" : 1.5528636107639667E-6,
"99.99" : 1.5528636107639667E-6,
"99.999" : 1.5528636107639667E-6,
"99.9999" : 1.5528636107639667E-6,
"100.0" : 1.5528636107639667E-6
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
1.5528636107639667E-6,
9.596786727520295E-7,
7.375483032591416E-7,
6.000565980573166E-7,
5.302584706779764E-7
],
[
1.3089866553403504E-6,
8.848246687708236E-7,
6.728363935992605E-7,
6.39243117656085E-7,
5.589492168761859E-7
],
[
1.5011027207012722E-6,
9.477396838941437E-7,
6.834675315679355E-7,
5.996515606679904E-7,
5.303842794047184E-7
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.linkedListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "10"
},
"primaryMetric" : {
"score" : 3.1518365953260005E-4,
"scoreError" : 2.650344132453461E-4,
"scoreConfidence" : [
5.0149246287253975E-5,
5.802180727779462E-4
],
"scorePercentiles" : {
"0.0" : 2.040936832220465E-9,
"50.0" : 3.258020124110451E-4,
"90.0" : 6.290582852453083E-4,
"95.0" : 6.511044004158607E-4,
"99.0" : 6.511044004158607E-4,
"99.9" : 6.511044004158607E-4,
"99.99" : 6.511044004158607E-4,
"99.999" : 6.511044004158607E-4,
"99.9999" : 6.511044004158607E-4,
"100.0" : 6.511044004158607E-4
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
5.799090206566678E-4,
2.8203434053498754E-4,
4.084852992147824E-4,
5.5462968000398634E-5,
2.040936832220465E-9
],
[
5.959891124894796E-4,
2.741420658031856E-4,
4.050103147846094E-4,
1.292647379800531E-4,
2.1377832450477853E-9
],
[
6.511044004158607E-4,
3.258020124110451E-4,
3.9017912291265433E-4
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.linkedListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "20"
},
"primaryMetric" : {
"score" : 1.266940848706251E-4,
"scoreError" : 1.067901481727194E-4,
"scoreConfidence" : [
1.9903936697905698E-5,
2.334842330433445E-4
],
"scorePercentiles" : {
"0.0" : 1.0570643716444602E-9,
"50.0" : 1.277025840062132E-4,
"90.0" : 2.873144880878097E-4,
"95.0" : 3.0071795274955784E-4,
"99.0" : 3.0071795274955784E-4,
"99.9" : 3.0071795274955784E-4,
"99.99" : 3.0071795274955784E-4,
"99.999" : 3.0071795274955784E-4,
"99.9999" : 3.0071795274955784E-4,
"100.0" : 3.0071795274955784E-4
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
2.7837884497997756E-4,
1.4004379809432887E-4,
1.690648751904472E-4,
3.979439739105633E-5,
1.0570643716444602E-9
],
[
2.539248775424261E-4,
1.2300171183989418E-4,
1.6619190221717098E-4,
9.198943682584735E-5,
3.341751395204592E-7
],
[
3.0071795274955784E-4,
1.277025840062132E-4,
1.712266253033709E-4,
3.8037901883119315E-5,
1.1328320741323733E-9
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.23",
"benchmark" : "cn.ideamake.im.auth.service.Test.linkedListAdd",
"mode" : "thrpt",
"threads" : 5,
"forks" : 3,
"jvm" : "/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/jre/bin/java",
"jvmArgs" : [
"-javaagent:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=61534:/Users/jeyyu/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "1.8.0_261",
"vmName" : "Java HotSpot(TM) 64-Bit Server VM",
"vmVersion" : "25.261-b12",
"warmupIterations" : 3,
"warmupTime" : "1 s",
"warmupBatchSize" : 100,
"measurementIterations" : 5,
"measurementTime" : "5 s",
"measurementBatchSize" : 1,
"params" : {
"length" : "30"
},
"primaryMetric" : {
"score" : 1.0018490369816409E-4,
"scoreError" : 7.4688462191315E-5,
"scoreConfidence" : [
2.549644150684908E-5,
1.748733658894791E-4
],
"scorePercentiles" : {
"0.0" : 2.423143548980252E-8,
"50.0" : 9.23894279692493E-5,
"90.0" : 2.1170862702798537E-4,
"95.0" : 2.220596797238462E-4,
"99.0" : 2.220596797238462E-4,
"99.9" : 2.220596797238462E-4,
"99.99" : 2.220596797238462E-4,
"99.999" : 2.220596797238462E-4,
"99.9999" : 2.220596797238462E-4,
"100.0" : 2.220596797238462E-4
},
"scoreUnit" : "ops/ns",
"rawData" : [
[
2.0480792523074482E-4,
8.962885218669426E-5,
1.1985924046108952E-4,
6.011791224746196E-5,
3.439292032454452E-8
],
[
1.8999885350868268E-4,
8.464552988300562E-5,
1.1405865213041896E-4,
7.856668953262646E-5,
2.423143548980252E-8
],
[
2.220596797238462E-4,
1.1198263143710367E-4,
1.345185179434591E-4,
9.23894279692493E-5,
8.101886226446768E-8
]
]
},
"secondaryMetrics" : {
}
}
]
本文到此结束,希望对你有帮助~