别再写 main 方法测试了,太 Low!这才是专业 Java 测试方法!

article/2025/6/11 15:56:35

accurate-builder-equipment-1573821.jpg 前言

"If you cannot measure it, you cannot improve it".

"If you cannot measure it, you cannot improve it".

在日常开发中,我们对一些代码的调用或者工具的使用会存在多种选择方式,在不确定他们性能的时候,我们首先想要做的就是去测量它。大多数时候,我们会简单的采用多次计数的方式来测量,来看这个方法的总耗时。

image.png

也就是说,JVM会不断的进行编译优化,这就使得很难确定重复多少次才能得到一个稳定的测试结果?所以,很多有经验的同学会在测试代码前写一段预热的逻辑。

JMH,全称 Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,是由 OpenJDK/Oracle 官方发布的工具。何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。

Java的基准测试需要注意的几个点:

  • 测试前需要预热。

  • 防止无用代码进入测试方法中。

  • 并发测试。

  • 测试结果呈现。

测试前需要预热。

防止无用代码进入测试方法中。

并发测试。

测试结果呈现。

JMH的使用场景:

  1. 定量分析某个热点函数的优化效果

  2. 想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性

  3. 对比一个函数的多种实现方式

定量分析某个热点函数的优化效果

想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性

对比一个函数的多种实现方式

本篇主要是介绍JMH的DEMO演示,和常用的注解参数。希望能对你起到帮助。

DEMO 演示

这里先演示一个DEMO,让不了解JMH的同学能够快速掌握这个工具的大概用法。

1. 测试项目构建

JMH是内置Java9及之后的版本。这里是以Java8进行说明。

为了方便,这里直接介绍使用maven构建JMH测试项目的方式。

第一种是使用命令行构建,在指定目录下执行以下命令:

$ mvn archetype:generate \

-DinteractiveMode=false \

-DarchetypeGroupId=org.openjdk.jmh \

-DarchetypeArtifactId=jmh-java-benchmark-archetype \

-DgroupId=org.sample \

-DartifactId=test \

-Dversion=1.0

复制代码

对应目录下会出现一个test项目,打开项目后我们会看到这样的项目结构。

image.png

第二种方式就是直接在现有的maven项目中添加 jmh-core 和 jmh-generator-annprocess 的依赖来集成JMH。

< dependency>

< groupId> org.openjdk.jmh </ groupId>

< artifactId> jmh-core </ artifactId>

< version> ${jmh.version} </ version>

</ dependency>

< dependency>

< groupId> org.openjdk.jmh </ groupId>

< artifactId> jmh-generator-annprocess </ artifactId>

< version> ${jmh.version} </ version>

< scope> provided </ scope>

</ dependency>

复制代码

2. 编写性能测试

这里我以测试LinkedList 通过index 方式迭代和foreach 方式迭代的性能差距为例子,编写测试类,涉及到的注解在之后会讲解,

/**

* @authorRichard_yyf

* @version1.0 2019/8/27

*/

@State(Scope.Benchmark)

@OutputTimeUnit(TimeUnit.SECONDS)

@Threads(Threads.MAX)

publicclassLinkedListIterationBenchMark{

privatestaticfinalintSIZE = 10000;

privateList<String> list = newLinkedList<>;

@Setup

publicvoidsetUp{

for( inti = 0; i < SIZE; i++) {

list.add(String.valueOf(i));

}

}

@Benchmark

@BenchmarkMode(Mode.Throughput)

publicvoidforIndexIterate{

for( inti = 0; i < list.size; i++) {

list.get(i);

System.out.print( "");

}

}

@Benchmark

@BenchmarkMode(Mode.Throughput)

publicvoidforEachIterate{

for(String s : list) {

System.out.print( "");

}

}

}

复制代码

3. 执行测试

运行 JMH基准测试有两种方式,一个是生产jar文件运行,另一个是直接写main函数或者放在单元测试中执行。

生成jar文件的形式主要是针对一些比较大的测试,可能对机器性能或者真实环境模拟有一些需求,需要将测试方法写好了放在linux环境执行。具体命令如下

$mvn clean install

$java -jar target/benchmarks.jar

复制代码

我们日常中遇到的一般是一些小测试,比如我上面写的例子,直接在IDE中跑就好了。启动方式如下:

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder

.include(LinkedListIterationBenchMark.class.getSimpleName)

.forks(1)

.warmupIterations(2)

.measurementIterations(2)

.output("E:/Benchmark.log")

.build;

new Runner(opt).run;

}

复制代码

4. 报告结果

输出结果如下,

最后的结果:

Benchmark Mode Cnt Score Error Units

LinkedListIterationBenchMark.forEachIterate thrpt 2 1192.380 ops/s

LinkedListIterationBenchMark.forIndexIterate thrpt 2 206.866 ops/s

复制代码

整个过程:

# Detecting actual CPU count: 12 detected

# JMH version: 1.21

# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11

# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe

# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8

# Warmup: 2 iterations, 10 s each

# Measurement: 2 iterations, 10 s each

# Timeout: 10 min per iteration

# Threads: 12 threads, will synchronize iterations

# Benchmark mode: Throughput, ops/time

# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forEachIterate

# Run progress: 0.00% complete, ETA 00:01:20

# Fork: 1 of 1

# Warmup Iteration 1: 1189.267 ops/s

# Warmup Iteration 2: 1197.321 ops/s

Iteration 1:1193.062ops/s

Iteration 2:1191.698ops/s

Result"org.sample.jmh.LinkedListIterationBenchMark.forEachIterate":

1192.380ops/s

# JMH version: 1.21

# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11

# VM invoker: C:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe

# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\lib\idea_rt.jar=65175:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.2\bin -Dfile.encoding=UTF-8

# Warmup: 2 iterations, 10 s each

# Measurement: 2 iterations, 10 s each

# Timeout: 10 min per iteration

# Threads: 12 threads, will synchronize iterations

# Benchmark mode: Throughput, ops/time

# Benchmark: org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate

# Run progress: 50.00% complete, ETA 00:00:40

# Fork: 1 of 1

# Warmup Iteration 1: 205.676 ops/s

# Warmup Iteration 2: 206.512 ops/s

Iteration 1:206.542ops/s

Iteration 2:207.189ops/s

Result"org.sample.jmh.LinkedListIterationBenchMark.forIndexIterate":

206.866ops/s

# Run complete. Total time: 00:01:21

REMEMBER:Thenumbersbelowarejustdata.Togainreusableinsights,youneedtofollowupon

whythenumbersarethewaytheyare.Useprofilers(see-prof,-lprof),designfactorial

experiments,performbaselineandnegativeteststhatprovideexperimentalcontrol,makesure

thebenchmarkingenvironmentissafeonJVM/OS/HWlevel,askforreviewsfromthedomainexperts.

Donotassumethenumberstellyouwhatyouwantthemtotell.

BenchmarkModeCntScoreErrorUnits

LinkedListIterationBenchMark.forEachIteratethrpt21192.380ops/s

LinkedListIterationBenchMark.forIndexIteratethrpt2206.866ops/s

复制代码

注解介绍

下面我们来详细介绍一下相关的注解,

@BenchmarkMode

微基准测试类型。 JMH提供了以下几种类型进行支持:

类型描述
Throughput每段时间执行的次数,一般是秒
AverageTime平均时间,每次操作的平均耗时
SampleTime在测试中,随机进行采样执行的时间
SingleShotTime在每次执行中计算耗时
All所有模式

可以注释在方法级别,也可以注释在类级别,

@BenchmarkMode(Mode.All)

public class LinkedListIterationBenchMark {

...

}

@Benchmark

@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})

public void m {

...

}

复制代码

@Warmup

这个单词的意思就是预热, iterations = 3 就是指预热轮数。

@Benchmark

@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})

@Warmup(iterations = 3)

public void m {

...

}

复制代码

@Measurement

正式度量计算的轮数。

  • iterations 进行测试的轮次

  • time 每轮进行的时长

  • timeUnit 时长单位

iterations 进行测试的轮次

time 每轮进行的时长

timeUnit 时长单位

@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})

@Measurement(iterations = 3)

public void m {

...

}

复制代码

@Threads

每个进程中的测试线程。

@Threads(Threads.MAX)

publicclassLinkedListIterationBenchMark{

...

}

复制代码

@Fork

进行 fork 的次数。如果 fork 数是3的话,则 JMH 会 fork 出3个进程来进行测试。

@Benchmark

@BenchmarkMode({Mode.Throughput, Mode.SingleShotTime})

@Fork(value = 3)

public void m {

...

}

复制代码

@OutputTimeUnit

基准测试结果的时间类型。一般选择秒、毫秒、微秒。

@OutputTimeUnit(TimeUnit.SECONDS)

publicclassLinkedListIterationBenchMark{

...

}

复制代码

@Benchmark

方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似。

@Param

属性级注解, @Param 可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。

@Setup

方法级注解,这个注解的作用就是我们需要在测试之前进行一些 准备工作,比如对一些数据的初始化之类的。

@TearDown

方法级注解,这个注解的作用就是我们需要在测试之后进行一些 结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。

@State

当使用 @Setup 参数的时候,必须在类上加这个参数,不然会提示无法运行。

就比如我上面的例子中,就必须设置 state 。

State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。

  1. Thread: 该状态为每个线程独享。

  2. Group: 该状态为同一个组里面所有线程共享。

  3. Benchmark: 该状态在所有线程间共享。

Thread: 该状态为每个线程独享。

Group: 该状态为同一个组里面所有线程共享。

Benchmark: 该状态在所有线程间共享。

在启动方法中,可以直接指定上述说到的一些参数,并且能将测试结果输出到指定文件中,

/**

* 仅限于IDE中运行

* 命令行模式 则是 build 然后 java -jar 启动

*

* 1. 这是benchmark 启动的入口

* 2. 这里同时还完成了JMH测试的一些配置工作

* 3. 默认场景下,JMH会去找寻标注了@Benchmark的方法,可以通过include和exclude两个方法来完成包含以及排除的语义

*/

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder

// 包含语义

// 可以用方法名,也可以用XXX.class.getSimpleName

.include("Helloworld")

// 排除语义

.exclude("Pref")

// 预热10轮

.warmupIterations(10)

// 代表正式计量测试做10轮,

// 而每次都是先执行完预热再执行正式计量,

// 内容都是调用标注了@Benchmark的代码。

.measurementIterations(10)

// forks(3)指的是做3轮测试,

// 因为一次测试无法有效的代表结果,

// 所以通过3轮测试较为全面的测试,

// 而每一轮都是先预热,再正式计量。

.forks(3)

.output("E:/Benchmark.log")

.build;

new Runner(opt).run;

}

复制代码

结语

基于JMH可以对很多工具和框架进行测试,比如日志框架性能对比、BeanCopy性能对比 等,更多的example可以参考官方给出的 JMH samples[1]

上面其实只是讲解了关于 JMH的使用,推荐延伸阅读这篇文章

JAVA拾遗 — JMH与8个测试陷阱[2]

作者从 Java Developer 角度来谈谈一些常见的代码测试陷阱,分析他们和操作系统底层以及 Java 底层的关联性,并借助 JMH来帮助大家摆脱这些陷阱。

参考

  1. hg.openjdk.java.net/code-tools/…[3]

  2. www.hollischuang.com/archives/10…[4]

  3. yq.aliyun.com/articles/34…[5]

  4. openjdk.java.net/projects/co…[6]

hg.openjdk.java.net/code-tools/…[3]

www.hollischuang.com/archives/10…[4]

yq.aliyun.com/articles/34…[5]

openjdk.java.net/projects/co…[6]

[1]

https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/: https://link.juejin.cn?target=https%3A%2F%2Fhg.openjdk.java.net%2Fcode-tools%2Fjmh%2Ffile%2Ftip%2Fjmh-samples%2Fsrc%2Fmain%2Fjava%2Forg%2Fopenjdk%2Fjmh%2Fsamples%2F

[2]

https://www.cnkirito.moe/java-jmh/: https://link.juejin.cn?target=https%3A%2F%2Fwww.cnkirito.moe%2Fjava-jmh%2F

[3]

http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/: https://link.juejin.cn?target=http%3A%2F%2Fhg.openjdk.java.net%2Fcode-tools%2Fjmh%2Ffile%2Ftip%2Fjmh-samples%2Fsrc%2Fmain%2Fjava%2Forg%2Fopenjdk%2Fjmh%2Fsamples%2F

[4]

http://www.hollischuang.com/archives/1072: https://link.juejin.cn?target=http%3A%2F%2Fwww.hollischuang.com%2Farchives%2F1072

[5][6]

https://openjdk.java.net/projects/code-tools/jmh/: https://link.juejin.cn?target=https%3A%2F%2Fopenjdk.java.net%2Fprojects%2Fcode-tools%2Fjmh%2F

• 面试官:聊一聊Java 泛型通配符 T,E,K,V,?

• 阿里一面:如何保证API接口数据安全?

• 0.2秒居然复制了100G文件?

• Java17,有史以来最快 JDK!

最近写了一套 6000 页的 Java 学习手册,以及珍藏四本 Java 人必读4大神器,分享到知乎已经 3 万赞了!

每篇文章图文并茂,附有源码。还有电子书合集

如果你想获得完整PDF可以通过以下方式获得

资料获取方法

  1. 即可领取
  2. 在后台回复关键词 002

明天见(。・ω・。)


http://www.hkcw.cn/article/JdirHCEpeH.shtml

相关文章

想要给孩子喝国产奶粉,伊利金领冠育护怎么样?

伊利金领冠育护奶粉是中国市场上备受好评的婴幼儿配方奶粉之一,其产品系列包括金领冠育护等。金领冠育护奶粉采用100%鲜活生牛乳,经过4度冷链运输,新鲜直达工厂。其活性蛋白OPN含量高达40000克牛乳中的1克,有助于减少发热和呼吸道不适的情况。此外,金领冠育护奶粉还含有多…

朱雀玄武敕令称猜对作文:能拿40分 高考改名引关注

24岁的朱雀玄武敕令因频繁改名而受到关注。6月7日和8日,他在湖南省桂阳县龙潭中学考点参加了2025年的高考。高考第二天,小朱上午考历史,下午考英语。他在考点外手持准考证拍照留念,并让监考老师帮他拍了一张照片。他提到天气不错,没有下雨。他的准考证上填写的名字是朱雀玄…

今天上午!台湾7.4级地震!霞浦震感强烈

中国地震台网自动测定:04月03日07时58分在中国台湾附近(北纬23.86度,东经121.62度)发生7.4级左右地震,最终结果以正式速报为准。霞浦震感强烈 有市民在床上被晃醒展开全文福建多地网友表示震感强烈浙江、江苏网友都有震感地震发生时应该怎么做? 室内 如果你在室内,应就近…

米丽娅姆·莱昂内在演艺生涯中6部惊艳之作

: 米丽娅姆莱昂内,这位新晋娱乐圈的明星,凭借其美貌和才华吸引了无数观众的目光。在这篇文章中,我们将为您揭示她生涯前期6部影视佳作中最亮眼的时刻。出生于意大利的西西里卡塔尼亚,米丽娅姆在23岁那年赢得了“意大利小姐”的称号,并随后成为了意大利国家电视台Rai 1台的…

声音| 中奖2.2亿不交税成历史!群众有呼声 政府有回应!

(点击图片查看) LATEST NEWS 中奖2.2亿不交税? 购彩者中奖2.2亿元不用纳税,曾在社会上引起较大争议,随着财政部、税务总局、民政部、体育总局在8月16日联合发布《关于彩票兑奖与适用税法有关口径的公告》,明确了“一次性中奖收入”的定义,中奖2.2亿元不用纳税将成为历史…

原创张国立的4个孩子,儿子是永远的耻辱,女儿却大红大紫

张国立,影视界的璀璨明珠,他的演技深邃如海,魅力独特,与王刚、张铁林并肩矗立,被赞誉为影视江湖中的铁三角。他们的缘分,仿佛是天意织就的锦绣,始于那部熠熠生辉的《铁齿铜牙纪晓岚》。这部作品,犹如夜空中绽放的烟花,瞬间点亮了他们三人的艺术星途,使他们在影视的天…

Tarzan再创奇迹!亲手淘汰老东家WBG的胜利之路

在昨晚的2024 LPL夏季赛季后赛中,Weibo Gaming(WBG)和LNG Esports的对决无疑是本次赛事的一大焦点。这场比赛不仅事关晋级败者组决赛的资格,还因为一个人而备受关注——那就是前LNG战队的明星打野选手Tarzan。 比赛开始前,社交媒体上已经充斥着关于这场赛事的讨论,尤其是…

原创业绩为王!2024年中报“预增王”名单新出炉,13股业绩增超1000%

中报“预增王”名单出炉! 目前中报预告已经基本披露完毕了! 而且从市场表现来看,中报业绩预喜的公司大部分出现一定程度的抗跌,甚至走出逆势上涨行情! 在目前市场震荡走低的背景,不排除接下来资金倾向于中报绩优的上市公司!毕竟自从新“国九条”发布以来,绩差问题股就开…

图片裁剪怎么弄?裁剪图片的四种极为简单的方法

图片裁剪怎么弄?裁剪图片是在编辑和美化图片时常见的操作,它可以帮助你去除不需要的部分,突出重点内容,或者改变图片的外观和比例。这个过程既简单又具有很大的创意空间,因此,掌握如何裁剪图片是提升你图像处理技能的重要一步。通过裁剪图片,可以去除无关或多余的部分,…

大爆发!北交所一天受理6家IPO,新受理企业已达15家

文/梧桐小新 6月26日,北交所新受理6家企业的IPO申请,分别是世昌股份(873702)、交大铁发(874047)、国亮新材(874134)、建院股份(870355)、爱舍伦(874105)、楚大智能(874280)。自6月21日恢复受理以来,北交所已累计有15家新受理的IPO企业,同期沪深交易所仅有2家。…

“创新增动力,赋能育未来”—才神道第一届锂电技术创新论坛开幕

2024-07-08 17:05:10作者:姚立伟 7月3日-5日,才神道首届国际锂电技术创新峰会,在上海新国际博览中心降重举行。天铁股份、中裕达、广汽埃安、亿纬锂能、宁德时代、新能安、蜂巢能源、卫蓝新能源、天赐、远景动力、浙能、大众、上汽、曼恩斯特、阿科玛、力神超电、上海熵延、洛…

又一院士名单公布,6位两院院士入选!

【重磅】经管世界学术科研资料库入口 来源:高绩 近日,发展中国家科学院(TWAS)公布新增院士名单,共增选出47位发展中国家科学院院士。发展中国家科学院原名为第三世界科学院(Third World Academy of Sciences,TWAS),成立于1983年11月10日,由诺贝尔物理学奖获得者、巴基斯…

精智未来CEO王俊奇荣获广州黄埔区“自主创业之星”

全黄埔区仅15名企业家入选入选企业家须具备突出的创业业绩 和良好的社会示范作用 精智未来通过实践探索出了 一条具有示范意义的创业之路王俊奇博士是耶鲁大学博士后、密歇根大学博士 于2021年归国成立精智未来公司 担任京津冀国家技术创新中心博导 凭借出色的科研水平和商业化…

上个世纪九十年代播出的二十四部国产电视剧,哪一部惊艳时光?

当时间进入到上个世纪九十年代,群众的日子逐渐得到质的提升,对精神文化生活的需求水涨船高,这刺激我国的电视事业突飞猛进,并进而引领电视剧制作和播出步入勃发阶段,电视剧制作的技术条件明显好于八十年代,生产数量更呈几何级数增加,市场化的运作手段也日益成熟,但是,…

【校园资讯】一起过个传统的端午节——小一班家长助教

每年农历五月五日是“端午节”,让孩子从小了解中国传统节日和习俗,感受传统节日的气氛,在端午节前夕,桓宇妈妈和依辰妈妈在家长助教课上给孩子们上了生动的一课,使孩子们对中国传统文化有了进一步的了解和兴趣。首先,孩子们在桓宇妈妈的讲解中知道了端午节的由来。*五彩绳…

原创THE9成员身高排名,安崎最矮,刘雨昕虞书欣孔雪儿一样高

9、安崎的身高是158cm,虽然身高是组合里最矮的,但实力却很强,小身材大能量,尤其是安琦的挑眉,那种自信与小妖娆很有个性。8、刘雨昕的身高是168cm,C位出道的刘雨昕不仅唱跳实力都很强,而且中性风打扮的她简直太酷了。 7虞书欣的身高是168cm,性格又作又可爱的虞书欣绝对…

他14岁参军,16岁长征,有“情报将军”之誉,是至今健在的开国将军,99岁

他14岁踏上参加红军的道路,开始闹革命,16岁又随红军开始了万里长征,历经千难万险,征服了长征。此外,他还参加过诸多战斗,一生戎马,立下赫赫功勋,同时他还是长达近半个世纪从事机要情报工作的革命战士,有“情报将军”之誉。现如今的他仍健在,此人到底是谁呢?他就是开…

原创国脚拜合拉木大胆指出国足与韩国队差距,引起热议

21岁的国脚拜合拉木已经代表国家队出战两场比赛,并成功攻入一球。他以敢于冲击的前锋风格、坚毅的拼搏态度备受球迷喜爱,被视为中国足球中少见的类型球员之一,身体素质过硬,备受期待。最近在接受央视采访时,他毫不犹豫地谈论了国足和韩国队之间存在的实际差距,这些言论引…

原创《水浒传》:最成功老师是谁?最失败的又是谁?

今天教师节,祝所有的老师节日快乐。 《水浒传》里的好汉,兄弟关系不少,师徒关系也不少。及时雨宋江与毛头星孔明、 独火星孔亮;豹子头林冲与操刀鬼曹正;霹雳火秦明与镇三山黄信;打虎将李忠与九纹龙史进;教头王进和九纹龙史进;青眼虎李云与笑面虎朱富;病大虫薛永与通臂…

《雷霆战神》攻略:新手必看入门攻略。3500游戏平台

大家好!玩了一段时间《雷霆战神》,今天给大家分享一下这款游戏初期战斗力提升的心得。 游戏职业:战士、法师、道士,游戏首选职业战士,战士血多防高,能保护后排法师、道士,第二职业最好是法师,输出高有高伤害,第三职业就道士。游戏人物角色界面包括:装备,角色,升阶系…