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遠不止這些功能,更多高級的用法推薦大家看官方例子