当前位置 博文首页 > Kevin.ZhangCG:Java基准性能测试--JMH使用介绍
JMH是Java Microbenchmark Harness的简称,一个针对Java做基准测试的工具,是由开发JVM的那群人开发的。想准确的对一段代码做基准性能测试并不容易,因为JVM层面在编译期、运行时对代码做很多优化,但是当代码块处于整个系统中运行时这些优化并不一定会生效,从而产生错误的基准测试结果,而这个问题就是JMH要解决的。
JMeter可能是最常用的性能测试工具。它既支持图形界面,也支持命令行,属于黑盒测试的范畴,对非开发人员比较友好,上手也非常容易。图形界面一般用于编写、调试测试用例,而实际的性能测试建议还是在命令行下运行。
很多场景下JMeter和JMH都可以做性能测试,但是对于严格意义上的基准测试来说,只有JMH才适合。JMeter的测试结果精度相对JVM较低、所以JMeter不适合于类级别的基准测试,更适合于对精度要求不高、耗时相对较长的操作。
总结: JMeter适合一些相对耗时的集成功能测试,如API接口的测试。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
<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(); } }
项目打包
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的基准测试主要经历了下面几个过程:
在对Springboot项目做JMH基准测试时可能会因为maven-shade-plugin插件的问题打包报错,需要在JMH的maven-shade-plugin的插件配置中添加id即可。项目的pom可能如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.7.RELEASE</version> <relativePath/> </parent> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.2</version> <executions> <execution> <!-- 需要在此处添加一个id标签,否则mvn package时会报错 --> <id>shade-all-dependency-jar</id> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> ... </configuration> </execution> </executions> </plugin> ... </project>
在测试代码中正常基于SpringBootApplication构建ConfigurableApplicationContext从而获取bean的方式获取对象测试即可。
public class StringRedisTemplateBenchmark { StringRedisTemplate redisTemplate; @Setup(Level.Trial) public void setUp() { redisTemplate = SpringApplication.run(SpringBootApplicationClass.class).getBean(StringRedisTemplate.class); } @Benchmark public void testGet() { redisTemplate.opsForValue().get("testkey"); } } @SpringBootApplication public class SpringBootApplicationClass { }
application.properties
lettuce.pool.maxTotal=50
lettuce.pool.maxIdle=10
lettuce.pool.minIdle=0
lettuce.sentinel.master=mymaster
lettuce.sentinel.nodes=10.xx.xx.xx:26379,10.xx.xx.xx:26379
lettuce.password=xxxxxx
JMH测试的相关配置大多是通过注解的方式体现的。具体每个注解的使用实例也可以参考官网http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
JMH benchmark支持如下几种测试模式: