JMH

督邮
• 阅读 171

JMH是什么
JMH是Java Microbenchmark Harness的简称,一个针对Java做基准测试的工具,是由开发JVM的那群人开发的。想准确的对一段代码做基准性能测试并不容易,因为JVM层面在编译期、运行时对代码做很多优化,但是当代码块处于整个系统中运行时这些优化并不一定会生效,从而产生错误的基准测试结果,而这个问题就是JMH要解决的。

JMH vs JMeter
JMeter可能是最常用的性能测试工具。它既支持图形界面,也支持命令行,属于黑盒测试的范畴,对非开发人员比较友好,上手也非常容易。图形界面一般用于编写、调试测试用例,而实际的性能测试建议还是在命令行下运行。

很多场景下JMeter和JMH都可以做性能测试,但是对于严格意义上的基准测试来说,只有JMH才适合。JMeter的测试结果精度相对JVM较低、所以JMeter不适合于类级别的基准测试,更适合于对精度要求不高、耗时相对较长的操作。

JMeter测试精度差: JMeter自身框架比较重,举个例子:使用JMH测试一个方法,平均耗时0.01ms,而使用JMeter测试的结果平均耗时20ms,相差200倍。
JMeter内置很多采样器:JMeter内置了支持多种网络协议的采样器,可以在不写Java代码的情况下实现很多复杂的测试。JMeter支持集群的方式运行,方便模拟多用户、高并发压力测试。
总结: JMeter适合一些相对耗时的集成功能测试,如API接口的测试。JMH适合于类或者方法的单元测试。

JMH基本用法
创建JMH项目
官方推荐为JMH基准测试创建单独的项目,最简单的创建JMH项目的方法就是基于maven项目原型的方式创建(如果是在windows环境下,需要对org.open.jdk.jmh这样带.的用双引号包裹)。

复制代码
mvn archetype:generate

      -DinteractiveMode=false
      -DarchetypeGroupId=org.openjdk.jmh
      -DarchetypeArtifactId=jmh-java-benchmark-archetype
      -DarchetypeVersion=1.21
      -DgroupId=com.jenkov
      -DartifactId=first-benchmark
      -Dversion=1.0

复制代码
可以看到生成的项目pom文件中主要是添加了两个jmh
的依赖和设置了maven-shade-plugin的编译方式(负责把项目的所有依赖jar包打入到目标jar包中,与springboot的实现方式类似)。
复制代码
<dependencies>

    <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>
</dependencies>

...
<plugin>

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
    <execution>
        <phase>package</phase>
        <goals>
            <goal>shade</goal>
        </goals>
        <configuration>
            <finalName>${uberjar.name}</finalName>
            <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                    <mainClass>org.openjdk.jmh.Main</mainClass>
                </transformer>
            </transformers>
            <filters>
                <filter>
                    <!--
                        Shading signed JARs will fail without this.
                        http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
                    -->
                    <artifact>*:*</artifact>
                    <excludes>
                        <exclude>META-INF/*.SF</exclude>
                        <exclude>META-INF/*.DSA</exclude>
                        <exclude>META-INF/*.RSA</exclude>
                    </excludes>
                </filter>
            </filters>
        </configuration>
    </execution>
</executions>

</plugin>
复制代码
生成的项目中已经包含了一个class文件MyBenchmark.java,如下:

复制代码
public class MyBenchmark {

@Benchmark
public void testMethod() {
    // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
    // Put your benchmark code here.
}

}
复制代码
编写基准测试代码
在上面生成的MyBenchmark类的testMethod中就可以添加基准测试的java代码,举例如下:测试AtomicInteger的incrementAndGet的基准性能。

复制代码
public class MyBenchmark {

static AtomicInteger integer = new AtomicInteger();

@Benchmark
public void testMethod() {
    // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
    // Put your benchmark code here.
    integer.incrementAndGet();
}

}
复制代码
JMH打包、运行
项目打包

mvn clean install
运行生成的目标jar包benchmark.jar:

复制代码
java -jar benchmark.jar

JMH version: 1.21

VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13

VM invoker: C:\Java\jdk1.8.0_181\jre\bin\java.exe

VM options: <none>

Warmup: 5 iterations, 10 s each

Measurement: 5 iterations, 10 s each

Timeout: 10 min per iteration

Threads: 1 thread, will synchronize iterations

Benchmark mode: Throughput, ops/time

Benchmark: org.sample.MyBenchmark.testMethod

Run progress: 0.00% complete, ETA 00:01:40

Fork: 1 of 1

Warmup Iteration 1: 81052462.185 ops/s

Warmup Iteration 2: 80152956.333 ops/s

Warmup Iteration 3: 81305026.522 ops/s

Warmup Iteration 4: 81740215.227 ops/s

Warmup Iteration 5: 82398485.097 ops/s

Iteration 1: 82176523.804 ops/s
Iteration 2: 81818881.730 ops/s
Iteration 3: 82812749.807 ops/s
Iteration 4: 82406672.531 ops/s
Iteration 5: 74270344.512 ops/s

Result "org.sample.MyBenchmark.testMethod":
80697034.477 ±(99.9%) 13903555.960 ops/s [Average]
(min, avg, max) = (74270344.512, 80697034.477, 82812749.807), stdev = 3610709.330
CI (99.9%): [66793478.517, 94600590.437] (assumes normal distribution)

Run complete. Total time: 00:01:41

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 Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 5 80697034.477 ± 13903555.960 ops/s
复制代码
从上面的日志我们大致可以了解到 JMH的基准测试主要经历了下面几个过程:

打印本次测试的配置,warmup:5轮;measurement:5轮;每轮:10s;启动1个线程做测试;基准测试指标:吞吐量(throughput,单位是s);测试方法MyBenchmark.testMethod
启动一个JVM进程做基准测试(也可以设置启动多个进程,减少随机因素的误差影响)
在JVM进程中先执行了5轮的预热(warmup),每轮10s,总共50s的预热时间。预热的数据不作为基准测试的参考。
测试了5轮,每轮10s,总共50s的测试时间
汇总测试数据、生成结果报表。最终结论是吞吐量(80697034.477 ±13903555.960 ops/s),其中80697034.477 是结果,13903555.960是误差范围。

点赞
收藏
评论区
推荐文章
捉虫大师 捉虫大师
4年前
盘点golang中的开发神器
本文已收录https://github.com/lkxiaolou/lkxiaolou欢迎star。在Java中,我们用Junit做单元测试,用JMH做性能基准测试(benchmark),用asyncprofiler剖析cpu性能,用jstack、jmap、arthas等来排查问题。作为一名比较新的编程语言,golang的这些工具是否更加好用呢?单元测
Wesley13 Wesley13
3年前
Java中对于位运算的优化以及运用与思考
引言随着JDK的发展以及JIT的不断优化,我们很多时候都可以写读起来易读但是看上去性能不高的代码了,编译器会帮我们优化代码。之前大学里面学单片机的时候,由于内存以及处理器性能都极其有限(可能很多时候考虑内存的限制优先于处理器),所以很多时候,利用位运算来节约空间或者提高性能,那么这些优秀的思想,放到目前的Java中,是否还有必要这么做呢?我们逐一思
Wesley13 Wesley13
3年前
MySQL基准测试(三)
MySQL基准测试(三)开源工具与实例演示针对web应用abab是一个ApacheHTTP服务的基准测试工具。http\_loadhttp\_load是一个针对Web服务器测试工具。JMeter是基于Java的应用程序,测试Web应
Wesley13 Wesley13
3年前
Java中的OutOfMemoryError的各种情况及解决和JVM内存结构
在JVM中内存一共有3种:Heap(堆内存),NonHeap(非堆内存)\3\和Native(本地内存)。\1\堆内存是运行时分配所有类实例和数组的一块内存区域。非堆内存包含方法区和JVM内部处理或优化所需的内存,存放有类结构(如运行时常量池、字段及方法结构,以及方法和构造函数代码)。本地内存是由操作系统管理的虚拟内存。当一个应用内存不足时
Stella981 Stella981
3年前
2019年Java学习之
我们在写java程序的时候,为了进行优化,把全部的精力用在了处理效率上,但是对IO的关注却很少。这也可能是由以前java早期时JVM在解释字节码时速度慢,运行速率大大低于本地编译代码,因此以前往往忽视了IO的优化。但是现在JVM在运行时优化已前进了一大步,现在的java应用程序更多的是受IO的束缚,也就是将时间花在等待数据传输上。现在有了NIO,就可以减
可莉 可莉
3年前
2019年Java学习之
我们在写java程序的时候,为了进行优化,把全部的精力用在了处理效率上,但是对IO的关注却很少。这也可能是由以前java早期时JVM在解释字节码时速度慢,运行速率大大低于本地编译代码,因此以前往往忽视了IO的优化。但是现在JVM在运行时优化已前进了一大步,现在的java应用程序更多的是受IO的束缚,也就是将时间花在等待数据传输上。现在有了NIO,就可以减
javalover123 javalover123
2年前
Java代码性能测试实战之ContiPerf
最近测试一个开源项目,发现生成的全局id有重复,方法加上synchronized提交PR后,有些同行对性能有疑虑,就准备做个代码性能测试。Java基准性能测试一般用JMH比较多,但是官方建议性能测试单独一个项目,感觉麻烦了点。发现ContiPerf可以方便的设置执行次数、时长、线程数、预热时长,还有Html格式报告,感觉还比较适合,基于Junit
警惕!自定义注解使用不当的排查实录
一、引言大家好,在日常开发过程中,Java注解(Annotation)是开发中经常使用的一个手段,用于给代码添加元数据的标记。它们可以提供代码额外的信息,这些信息可以在编译时或运行时被访问。注解不会改变代码的执行逻辑,但可以被编译器、JVM或框架等工具用于
聊聊JVM如何优化
首先应该明确的是JVM调优不是常规手段,JVM的存在本身就是为了减轻开发对于内存管理的负担,当出现性能问题的时候第一时间考虑的是代码逻辑与设计方案,以及是否达到依赖中间件的瓶颈,最后才是针对JVM进行优化。1.JVM内存模型针对JAVA8的模型进行讨论,J
京东云开发者 京东云开发者
3个月前
业务监控—一站式搭建jmeter+telegraf+influxdb+Grafana看板
京东物流:樊芳渝一、前言当前所测试业务需求为集成在业务系统WMS的jar包,jar包测试主要集中在本地拉取开发编写的代码做单元测试,因为jar包没有单独的应用,每当大促压测或日常压测,架构师或开发同事问道:这个jar包的JVM指标如何?jar包的火焰图是什
京东云开发者 京东云开发者
1星期前
业务监控—一站式搭建jmeter+telegraf+influxdb+Grafana看板
一、前言当前所测试业务需求为集成在业务系统WMS的jar包,jar包测试主要集中在本地拉取开发编写的代码做单元测试,因为jar包没有单独的应用,每当大促压测或日常压测,架构师或开发同事问道:这个jar包的JVM指标如何?jar包的火焰图是什么样的?对此,只