JMH我也是头次才知道,人家2013年就出来了,作为一个老程序员略感尴尬,一直奔波于项目开发并没有深究技术本身,这次也是在研究多线程锁的时候发现了JMH这个工具, JMH是Java Microbenchmark Harness的简称 ,我们平时写性能测试执行时长都是记录当前时间然后在减去执行完之后的当前时间,以此得出花费时间,这种方式实际上是非常不可靠的,因为java的代码在运行的时候编译器会对语法进行各种优化,而且每次JVM启动的时机都是会影响准确性的,因为进程在操作系统中也是有优先级的,真正的测试应该是在jvm执行的时候准确测试,这时候传统的时间测试就不可行,JMH的诞生就是解决这个问题
一般对代码测试我们会想知道什么呢?比如我这个方法在一秒钟能执行多少次,这个方法有几百人相当于几百个线程并发的时候的执行效率,这个方法一次执行的时间或者每次执行的时间,或者在百分之多少的概率下执行时长,TPS(吞吐量),OPS(每秒可操作次数)等等。
JMH就可以对一个方法的性能做到全方位的测试甚至精确到毫秒,微妙,纳秒级别,也可以用很多个线程去执行这个方法,甚至可以用@State注解对方法添加执行前后的钩子,下面就实际用代码演示以下
一、添加JMH Maven依赖
在pom文件中添加如下依赖,需要注意jmh-generator-annprocess的作用域设置为provided,不然可能会报 Unable to find the resource: /META-INF/BenchmarkList 的错误
<code><
dependency
><
groupId
>org.openjdk.jmhgroupId
><
artifactId
>jmh-coreartifactId
><
version
>1.21version
>dependency
><
dependency
><
groupId
>org.openjdk.jmhgroupId
><
artifactId
>jmh-generator-annprocessartifactId
><
version
>1.21version
><
scope
>providedscope
>dependency
>/<code>
二、编写简单的JMH测试类
这里写一个最简单的基准性能测试方法,用一个空方法来理解JMH的用法,首先在空方法上加入了@Benchmark注解,此注解表示那个方法用于基准测试。
然后在main方法中用配置项的方式配置基准测试的一些参数,默认的参数因为太大,测试起来比较慢,这里全部改为最小的,可以方便快速测试看结果。
<code>package
jmh;import
org.openjdk.jmh.annotations.Benchmark;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
org.openjdk.jmh.runner.options.TimeValue;public
class
SimpleBenchmark
{public
void
testBenchMark
()
{ }public
static
void
main
(String[] args)
throws
RunnerException { Options optionsBuilder=new
OptionsBuilder() .include(SimpleBenchmark.
class
.getSimpleName
()) .forks
(1) //设置JVM
进程启动数量,多个进程可以减少对测试结果的影响 .warmupIterations
(1) //预热次数 .warmupTime
(TimeValue
.seconds
(1)) //预热时间 .measurementIterations
(1) //基准测试次数 .measurementTime
(TimeValue
.seconds
(1)) //基准测试时间 .build
();new
Runner(optionsBuilder).run(); } }/<code>
观察输出结果
可以看到JMH的一些版本信息以及JVM的一些信息,我们的基准测试配置信息都加了注释说明
<code> Iteration 1: 3892608771.297 ops/s Result "jmh.SimpleBenchmark.testBenchMark": 3892608771.297 ops/s 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 baselineand
negative tests that provide experimental control, make sure the benchmarking environmentis
safe
on
JVM/OS/HWlevel
, askfor
reviewsfrom
thedomain
experts.Do
not
assume the numbers tell you what you want themto
tell.Benchmark
Mode
Cnt ScoreError
Units SimpleBenchmark.testBenchMark thrpt3892608771.297
ops/s Process finishedwith
exit
code0
/<code>
三、采用注解的方式测试
下面的代码加入了JMH常用的一些注解用于测试,后面有注解的详细解释,这里的代码加了休眠2秒可以更方便呢观察测试结果。
<code>package
jmh;import
org.openjdk.jmh.annotations.*;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
java.util.concurrent.TimeUnit;import
java.util.concurrent.atomic.AtomicInteger; ({Mode.Throughput,Mode.AverageTime,Mode.SampleTime,Mode.SingleShotTime}) (TimeUnit.SECONDS)public
class
MyBenchmark
{static
AtomicInteger atomicInteger=new
AtomicInteger(); (value =0
) (iterations =5
,time =1
) (iterations =5
,time =1
) (value =1
)public
void
testMethod
(TestState testState)
throws
InterruptedException { testState.getInteger().incrementAndGet(); TimeUnit.MILLISECONDS.sleep(2000
); atomicInteger.incrementAndGet(); } (Scope.Benchmark)public
static
class
TestState
{private
AtomicInteger integer; (Level.Trial)public
void
setup
()
{ System.out.printf("benchmark前"
); } (Level.Trial)public
void
tearDown
()
{ System.out.printf("benchmark后"
); }public
AtomicIntegergetInteger
()
{return
atomicInteger; } }public
static
void
main
(String[] args)
throws
RunnerException { Options options=new
OptionsBuilder() .include(MyBenchmark.
class
.getSimpleName
()) .build
() ;new
Runner(options).run(); } }/<code>
四、JMH注解介绍
五、输出测试结果
这里截图只看最终的结果,每个模式花费的时间都有说明,第一个是吞吐量,第二个平均时间,中间那些都是在采样百分之多少的时候花费的时间,因为我们休眠了2秒钟,所以大部分都是2秒一次的操作。
当然JMH远不止这些功能,更多高级的用法推荐大家看官方例子