作為軟件開發(fā)人員荚守,我們通常會寫一些測試程序用來對比不同算法珍德、不同工具的性能問題。而最常見的做法是寫一個 main 方法矗漾,構(gòu)造模擬場景進(jìn)行并發(fā)測試锈候。
如果細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn),每次測試結(jié)果誤差很大敞贡,有時候測試出的結(jié)果甚至與事實相反泵琳。當(dāng)然,這不排除是因為軟硬件環(huán)境因素導(dǎo)致誊役,但更多的可能是因為所使用測試方法自身有問題获列。
比如,不同需要性能比較方法放到一個虛擬機(jī)里調(diào)用蛔垢,有可能會互相影響击孩,缺少預(yù)熱的過程等。
本文給大家推薦一款 JDK9 及以后自帶的一款可用于軟件基準(zhǔn)測試的工具 JMH(Java Microbenchmark Harness)鹏漆。
JMH 簡介
JMH 是用于代碼微基準(zhǔn)測試的工具套件巩梢,主要是基于方法層面的基準(zhǔn)測試,精度可以達(dá)到納秒級甫男。
何謂 Micro Benchmark 呢且改?簡單的來說就是基于方法層面的基準(zhǔn)測試,精度可以達(dá)到微秒級板驳。當(dāng)你定位到熱點方法又跛,希望進(jìn)一步優(yōu)化方法性能的時候,就可以使用 JMH 對優(yōu)化的結(jié)果進(jìn)行量化的分析若治。
這款工具是由 Oracle 內(nèi)部實現(xiàn) JIT 的作者所寫慨蓝。我們知道 JIT(Java 即時編譯器)是將 JVM 優(yōu)化的所有高效手段和技術(shù)都使用上的地方感混。可想而知礼烈,開發(fā)者比任何人都更加了解 JVM 和 JIT 對基準(zhǔn)測試的影響弧满。
因此,這款工具是值得我們信賴和在實踐中進(jìn)行使用的此熬。而且使用起來也非常方便庭呜。
使用場景
JMH 不僅能幫我們測試一些常見類的性能,比如對比 StringBuffer 和 StringBuilder 的性能犀忱、對比不同算法的在不同數(shù)據(jù)量的性能等募谎,還能夠幫助我們對系統(tǒng)中發(fā)現(xiàn)的熱點代碼進(jìn)行量化分析。
JMH 通常用于以下應(yīng)用場景:
測試某個方法在穩(wěn)定執(zhí)行的情況下所需時間阴汇,以及執(zhí)行時間和問題規(guī)模的相關(guān)性数冬;
對比接口不同實現(xiàn)在給定條件下的吞吐量
查看多少百分比的請求在多長時間內(nèi)完成
使用實例
依賴引入
如果你使用的是 JDK9 或以上版本,則 JDK 中已經(jīng)自帶了該工具搀庶,直接使用即可拐纱。如果你使用的是其他版本則可以通過 maven 直接引入以下依賴:
? ? org.openjdk.jmh? ? jmh-core? ? 1.27? ? org.openjdk.jmh? ? jmh-generator-annprocess? ? 1.27復(fù)制代碼
其中 1.27 是當(dāng)前的最新版本,可根據(jù)實際需要更新或降低版本哥倔。
測試案例
下面以 StringBuffer 和 StringBuilder 的性能測試對比為例來進(jìn)行基準(zhǔn)測試秸架。
//使用模式 默認(rèn)是Mode.Throughput@BenchmarkMode(Mode.AverageTime)// 配置預(yù)熱次數(shù),默認(rèn)是每次運行1秒咆蒿,運行10次咕宿,這里設(shè)置為3次@Warmup(iterations = 3, time = 1)// 本例是一次運行4秒,總共運行3次蜡秽,在性能對比時候,采用默認(rèn)1秒即可@Measurement(iterations = 3, time = 4)// 配置同時起多少個線程執(zhí)行@Threads(1)//代表啟動多個單獨的進(jìn)程分別測試每個方法缆镣,這里指定為每個方法啟動一個進(jìn)程@Fork(1)// 定義類實例的生命周期芽突,Scope.Benchmark:所有測試線程共享一個實例,用于測試有狀態(tài)實例在多線程共享下的性能@State(value = Scope.Benchmark)// 統(tǒng)計結(jié)果的時間單元@OutputTimeUnit(TimeUnit.NANOSECONDS)public class JmhTest {? ? @Param(value = {"10", "50", "100"})? ? private int length;? ? public static void main(String[] args) throws RunnerException {? ? ? ? Options opt = new OptionsBuilder()? ? ? ? ? ? ? ? .include(JmhTest.class.getSimpleName())? ? ? ? ? ? ? ? .result("result.json")? ? ? ? ? ? ? ? .resultFormat(ResultFormatType.JSON).build();? ? ? ? new Runner(opt).run();? ? }? ? @Benchmark? ? public void testStringBufferAdd(Blackhole blackhole) {? ? ? ? StringBuffer sb = new StringBuffer();? ? ? ? for (int i = 0; i < length; i++) {? ? ? ? ? ? sb.append(i);? ? ? ? }? ? ? ? blackhole.consume(sb.toString());? ? }? ? @Benchmark? ? public void testStringBuilderAdd(Blackhole blackhole) {? ? ? ? StringBuilder sb = new StringBuilder();? ? ? ? for (int i = 0; i < length; i++) {? ? ? ? ? ? sb.append(i);? ? ? ? }? ? ? ? blackhole.consume(sb.toString());? ? }}復(fù)制代碼
上面介紹概念時已經(jīng)提到 Benchmark 為基準(zhǔn)測試董瞻,在使用中只需對要測試的方法添加 @Benchmark 注解即可寞蚌。而在測試類 JmhTest 指定測試的預(yù)熱、線程钠糊、測試維度等信息挟秤。
main 方法中通過 OptionsBuilder 構(gòu)造測試配置對象 Options,并傳入 Runner抄伍,啟動測試艘刚。這里指定測試結(jié)果為 json 格式,同時會將結(jié)果存儲在 result.json 文件當(dāng)中截珍。
執(zhí)行測試
執(zhí)行 main 方法攀甚,控制臺首先會打印出如下信息:
# JMH version: 1.27# VM version: JDK 1.8.0_271, Java HotSpot(TM) 64-Bit Server VM, 25.271-b09# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/bin/java# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56800:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8# JMH blackhole mode: full blackhole + dont-inline hint# Warmup: 3 iterations, 1 s each# Measurement: 3 iterations, 4 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Average time, time/op# Benchmark: com.choupangxia.strings.JmhTest.testStringBufferAdd# Parameters: (length = 10)復(fù)制代碼
這些信息主要用來展示測試的基本信息箩朴,包括 jdk、JVM秋度、預(yù)熱配置炸庞、執(zhí)行輪次、執(zhí)行時間荚斯、執(zhí)行線程埠居、測試的統(tǒng)計單位等。
# Warmup Iteration? 1: 76.124 ns/op# Warmup Iteration? 2: 77.703 ns/op# Warmup Iteration? 3: 249.515 ns/op復(fù)制代碼
這是對待測試方法的預(yù)熱處理事期,這部分不會記入測試結(jié)果滥壕。預(yù)熱主要讓 JVM 對被測代碼進(jìn)行足夠多的優(yōu)化,比如 JIT 編譯器的優(yōu)化刑赶。
Iteration? 1: 921.191 ns/opIteration? 2: 897.729 ns/opIteration? 3: 890.245 ns/opResult "com.choupangxia.strings.JmhTest.testStringBuilderAdd":? 903.055 ±(99.9%) 294.557 ns/op [Average]? (min, avg, max) = (890.245, 903.055, 921.191), stdev = 16.146? CI (99.9%): [608.498, 1197.612] (assumes normal distribution)復(fù)制代碼
顯示每次(共 3 次)迭代執(zhí)行速率捏浊,最后進(jìn)行統(tǒng)計。這里是對 testStringBuilderAdd 方法執(zhí)行 length 為 100 的測試撞叨,通過 (min, avg, max) 三項可以看出最小時間金踪、平均時間、最大時間的值牵敷,單位為 ns胡岔。stdev 顯示的是誤差時間。
通常情況下枷餐,我們只用看最后的結(jié)果即可:
Benchmark? ? ? ? ? ? ? ? ? ? (length)? Mode? Cnt? ? Score? ? ? Error? UnitsJmhTest.testStringBufferAdd? ? ? ? ? ? ? 10? avgt? ? 3? ? 92.599 ±? 105.019? ns/opJmhTest.testStringBufferAdd? ? ? ? ? ? ? 50? avgt? ? 3? 582.974 ±? 580.536? ns/opJmhTest.testStringBufferAdd? ? ? ? ? ? ? 100? avgt? ? 3? 1131.460 ± 1109.380? ns/opJmhTest.testStringBuilderAdd? ? ? ? 10? avgt? ? 3? ? 76.072 ±? ? 2.824? ns/opJmhTest.testStringBuilderAdd? ? ? ? 50? avgt? ? 3? 450.325 ±? 14.271? ns/opJmhTest.testStringBuilderAdd? ? ? 100? avgt? ? 3? 903.055 ±? 294.557? ns/op復(fù)制代碼
看到上述結(jié)果我們可能會很吃驚靶瘸,我們知道 StringBuffer 要比 StringBuilder 的性能低一些,但結(jié)果發(fā)現(xiàn)它們的之間的差別并不是很大毛肋。這是因為 JIT 編譯器進(jìn)行了優(yōu)化怨咪,比如當(dāng) JVM 發(fā)現(xiàn)在測試當(dāng)中 StringBuffer 并沒有發(fā)生逃逸,于是就進(jìn)行了鎖消除操作润匙。
常用注解
下面對 JHM 當(dāng)中常用的注解進(jìn)行說明诗眨,以便大家可以更精確的使用。
@BenchmarkMode
配置 Mode 選項孕讳,作用于類或者方法上匠楚,其 value 屬性為 Mode 數(shù)組,可同時支持多種 Mode厂财,如:@BenchmarkMode({Mode.SampleTime, Mode.AverageTime})芋簿,也可設(shè)為 Mode.All,即全部執(zhí)行一遍璃饱。
org.openjdk.jmh.annotations.Mode 為枚舉類与斤,對應(yīng)的源代碼如下:
public enum Mode {? ? Throughput("thrpt", "Throughput, ops/time"),? ? AverageTime("avgt", "Average time, time/op"),? ? SampleTime("sample", "Sampling time"),? ? SingleShotTime("ss", "Single shot invocation time"),? ? All("all", "All benchmark modes");? ? // 省略其他內(nèi)容}復(fù)制代碼
不同模式之間,測量的維度或測量的方式不同。目前 JMH 共有四種模式:
Throughput:整體吞吐量幽告,例如 “1 秒內(nèi)可以執(zhí)行多少次調(diào)用”梅鹦,單位為 ops/time;
AverageTime:調(diào)用的平均時間冗锁,例如 “每次調(diào)用平均耗時 xxx 毫秒”齐唆,單位為 time/op;
SampleTime:隨機(jī)取樣冻河,最后輸出取樣結(jié)果的分布箍邮,,例如 “99% 的調(diào)用在 xxx 毫秒以內(nèi)叨叙,99.99% 的調(diào)用在 xxx 毫秒以內(nèi)”锭弊;
SingleShotTime:以上模式都是默認(rèn)一次 iteration 是 1s,只有 SingleShotTime 是只運行一次擂错。往往同時把 warmup 次數(shù)設(shè)為 0味滞,用于測試?yán)鋯訒r的性能;
All:上面的所有模式都執(zhí)行一次钮呀;
@Warmup
在執(zhí)行 @Benchmark 之前進(jìn)行預(yù)熱操作剑鞍,確保測試的準(zhǔn)確性,可用于類或者方法上爽醋。默認(rèn)是每次運行 1 秒蚁署,運行 10 次。
其中 @Warmup 有以下屬性:
iterations:預(yù)熱的次數(shù)蚂四;Iteration 是 JMH 進(jìn)行測試的最小單位光戈,在大部分模式下,一次 iteration 代表的是一秒遂赠,JMH 會在這一秒內(nèi)不斷調(diào)用需要 benchmark 的方法久妆,然后根據(jù)模式對其采樣,計算吞吐量跷睦,計算平均執(zhí)行時間等镇饺。
time:每次預(yù)熱的時間;
timeUnit:時間的單位送讲,默認(rèn)秒;
batchSize:批處理大小惋啃,每次操作調(diào)用幾次方法哼鬓;
JIT 在執(zhí)行的過程中會將熱點代碼編譯為機(jī)器碼,并進(jìn)行各種優(yōu)化边灭,從而提高執(zhí)行效率异希。預(yù)熱的主要目的是讓 JVM 的 JIT 機(jī)制生效,讓結(jié)果更接近真實效果绒瘦。
@State
類注解称簿,JMH 測試類必須使用 @State 注解扣癣,不然會提示無法運行。
State 定義了一個類實例的生命周期(作用范圍)憨降,可以類比 Spring Bean 的 Scope父虑。因為很多 benchmark 會需要一些表示狀態(tài)的類,JMH 會根據(jù) scope 來進(jìn)行實例化和共享操作授药。
@State 可以被繼承使用士嚎,如果父類定義了該注解,子類則無需定義悔叽。
由于 JMH 允許多線程同時執(zhí)行測試莱衩,不同的選項含義如下:
Scope.Thread:默認(rèn)的 State,該狀態(tài)為每個線程獨享娇澎,每個測試線程分配一個實例笨蚁;
Scope.Benchmark:該狀態(tài)在所有線程間共享,所有測試線程共享一個實例趟庄,用于測試有狀態(tài)實例在多線程共享下的性能括细;
Scope.Group:該狀態(tài)為同一個組里面所有線程共享。
@OutputTimeUnit
benchmark 統(tǒng)計結(jié)果所使用的時間單位岔激,可用于類或者方法注解勒极,使用 java.util.concurrent.TimeUnit 中的標(biāo)準(zhǔn)時間單位。
@Measurement
度量虑鼎,其實就是實際調(diào)用方法所需要配置的一些基本測試參數(shù)辱匿,可用于類或者方法上。配置屬性項目和作用與 @Warmup 相同炫彩。
一般比較重的程序可以進(jìn)行大量的測試匾七,放到服務(wù)器上運行。在性能對比時江兢,采用默認(rèn) 1 秒即可昨忆,如果用 jvisualvm 做性能監(jiān)控,可以指定一個較長時間運行杉允。
@Threads
每個進(jìn)程中同時起多少個線程執(zhí)行邑贴,可用于類或者方法上。默認(rèn)值是 Runtime.getRuntime().availableProcessors()叔磷,根據(jù)具體情況選擇拢驾,一般為 cpu 乘以 2。
@Fork
代表啟動多個單獨的進(jìn)程分別測試每個方法改基,可用于類或者方法上繁疤。如果 fork 數(shù)是 2 的話,則 JMH 會 fork 出兩個進(jìn)程來進(jìn)行測試。
JVM 因為使用了 profile-guided optimization 而 “臭名昭著”稠腊,這對于微基準(zhǔn)測試來說十分不友好躁染,因為不同測試方法的 profile 混雜在一起,“互相傷害” 彼此的測試結(jié)果架忌。對于每個 @Benchmark 方法使用一個獨立的進(jìn)程可以解決這個問題吞彤,這也是 JMH 的默認(rèn)選項。注意不要設(shè)置為 0鳖昌,設(shè)置為 n 則會啟動 n 個進(jìn)程執(zhí)行測試(似乎也沒有太大意義)备畦。fork 選項也可以通過方法注解以及啟動參數(shù)來設(shè)置。
@Param
屬性級注解许昨,指定某項參數(shù)的多種情況懂盐,特別適合用來測試一個函數(shù)在不同的參數(shù)輸入的情況下的性能,只能作用在字段上糕档,使用該注解必須定義 @State 注解莉恼。
@Param 注解接收一個 String 數(shù)組,在 @Setup 方法執(zhí)行前轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型速那。多個 @Param 注解的成員之間是乘積關(guān)系俐银,譬如有兩個用 @Param 注解的字段,第一個有 5 個值端仰,第二個字段有 2 個值捶惜,那么每個測試方法會跑 5*2=10 次。
@Benchmark
方法注解荔烧,表示該方法是需要進(jìn)行 benchmark 的對象吱七,用法和 JUnit 的 @Test 類似。
@Setup
方法注解鹤竭,這個注解的作用就是我們需要在測試之前進(jìn)行一些準(zhǔn)備工作踊餐,比如對一些數(shù)據(jù)的初始化之類的。
@TearDown
方法注解臀稚,與 @Setup 相對的吝岭,會在所有 benchmark 執(zhí)行結(jié)束以后執(zhí)行,比如關(guān)閉線程池吧寺,數(shù)據(jù)庫連接等的窜管,主要用于資源的回收等。
Threads
每個 fork 進(jìn)程使用多少個線程去執(zhí)行測試方法稚机,默認(rèn)值是 Runtime.getRuntime().availableProcessors()微峰。
@Group
方法注解,可以把多個 benchmark 定義為同一個 group抒钱,則它們會被同時執(zhí)行,譬如用來模擬生產(chǎn)者-消費者讀寫速度不一致情況下的表現(xiàn)。
@Level
用于控制 @Setup谋币,@TearDown 的調(diào)用時機(jī)仗扬,默認(rèn)是 Level.Trial。
Trial:每個 benchmark 方法前后蕾额;
Iteration:每個 benchmark 方法每次迭代前后早芭;
Invocation:每個 benchmark 方法每次調(diào)用前后,謹(jǐn)慎使用诅蝶,需留意 javadoc 注釋退个;
JMH 注意事項
無用代碼消除(Dead Code Elimination)
現(xiàn)代編譯器是十分聰明的,它們會對代碼進(jìn)行推導(dǎo)分析调炬,判定哪些代碼是無用的然后進(jìn)行去除语盈,這種行為對微基準(zhǔn)測試是致命的,它會使你無法準(zhǔn)確測試出你的方法性能缰泡。
JMH 本身已經(jīng)對這種情況做了處理刀荒,要記住:1. 永遠(yuǎn)不要寫 void 方法棘钞;2. 在方法結(jié)束返回計算結(jié)果缠借。有時候如果需要返回多于一個結(jié)果,可以考慮自行合并計算結(jié)果宜猜,或者使用 JMH 提供的 BlackHole 對象:
/* * This demonstrates Option A: * * Merge multiple results into one and return it. * This is OK when is computation is relatively heavyweight, and merging * the results does not offset the results much. */@Benchmarkpublic double measureRight_1() {? ? return Math.log(x1) + Math.log(x2);}/* * This demonstrates Option B: * * Use explicit Blackhole objects, and sink the values there. * (Background: Blackhole is just another @State object, bundled with JMH). */@Benchmarkpublic void measureRight_2(Blackhole bh) {? ? bh.consume(Math.log(x1));? ? bh.consume(Math.log(x2));}復(fù)制代碼
再比如下面代碼:
@Benchmarkpublic void testStringAdd(Blackhole blackhole) {? ? String a = "";? ? for (int i = 0; i < length; i++) {? ? ? ? a += i;? ? }}復(fù)制代碼
JVM 可能會認(rèn)為變量 a 從來沒有使用過泼返,從而進(jìn)行優(yōu)化把整個方法內(nèi)部代碼移除掉,這就會影響測試結(jié)果姨拥。
JMH 提供了兩種方式避免這種問題绅喉,一種是將這個變量作為方法返回值 return a,一種是通過 Blackhole 的 consume 來避免 JIT 的優(yōu)化消除垫毙。
常量折疊(Constant Folding)
常量折疊是一種現(xiàn)代編譯器優(yōu)化策略霹疫,例如,i = 320 * 200 * 32综芥,多數(shù)的現(xiàn)代編譯器不會真的產(chǎn)生兩個乘法的指令再將結(jié)果儲存下來丽蝎,取而代之的,它們會辨識出語句的結(jié)構(gòu)膀藐,并在編譯時期將數(shù)值計算出來(i = 2,048,000)屠阻。
在微基準(zhǔn)測試中,如果你的計算輸入是可預(yù)測的额各,也不是一個 @State 實例變量国觉,那么很可能會被 JIT 給優(yōu)化掉。對此虾啦,JMH 的建議是:1. 永遠(yuǎn)從 @State 實例中讀取你的方法輸入麻诀;2. 返回你的計算結(jié)果痕寓;3. 或者考慮使用 BlackHole 對象;
見如下官方例子:
@State(Scope.Thread)@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)public class JMHSample_10_ConstantFold {? ? private double x = Math.PI;? ? private final double wrongX = Math.PI;? ? @Benchmark? ? public double baseline() {? ? ? ? // simply return the value, this is a baseline? ? ? ? return Math.PI;? ? }? ? @Benchmark? ? public double measureWrong_1() {? ? ? ? // This is wrong: the source is predictable, and computation is foldable.? ? ? ? return Math.log(Math.PI);? ? }? ? @Benchmark? ? public double measureWrong_2() {? ? ? ? // This is wrong: the source is predictable, and computation is foldable.? ? ? ? return Math.log(wrongX);? ? }? ? @Benchmark? ? public double measureRight() {? ? ? ? // This is correct: the source is not predictable.? ? ? ? return Math.log(x);? ? }? ? public static void main(String[] args) throws RunnerException {? ? ? ? Options opt = new OptionsBuilder()? ? ? ? ? ? ? ? .include(JMHSample_10_ConstantFold.class.getSimpleName())? ? ? ? ? ? ? ? .warmupIterations(5)? ? ? ? ? ? ? ? .measurementIterations(5)? ? ? ? ? ? ? ? .forks(1)? ? ? ? ? ? ? ? .build();? ? ? ? new Runner(opt).run();? ? }}復(fù)制代碼
循環(huán)展開(Loop Unwinding)
循環(huán)展開最常用來降低循環(huán)開銷蝇闭,為具有多個功能單元的處理器提供指令級并行呻率。也有利于指令流水線的調(diào)度。例如:
for (i = 1; i <= 60; i++)? ? a[i] = a[i] * b + c;復(fù)制代碼
可以展開成:
for (i = 1; i <= 60; i+=3){? a[i] = a[i] * b + c;? a[i+1] = a[i+1] * b + c;? a[i+2] = a[i+2] * b + c;}復(fù)制代碼
由于編譯器可能會對你的代碼進(jìn)行循環(huán)展開呻引,因此 JMH 建議不要在你的測試方法中寫任何循環(huán)礼仗。如果確實需要執(zhí)行循環(huán)計算,可以結(jié)合 @BenchmarkMode(Mode.SingleShotTime) 和 @Measurement(batchSize = N) 來達(dá)到同樣的效果逻悠。參考如下例子:
/* * Suppose we want to measure how much it takes to sum two integers: */int x = 1;int y = 2;/* * This is what you do with JMH. */@Benchmark@OperationsPerInvocation(100)public int measureRight() {? ? return (x + y);}復(fù)制代碼
JMH 可視化
在示例的 main 方法中指定了生成測試結(jié)果的輸出文件 result.json元践,其中的內(nèi)容就是控制臺輸出的相關(guān)內(nèi)容以 json 格式存儲。
針對 json 格式的內(nèi)容童谒,可以在其他網(wǎng)站上以圖表的形式可視化展示单旁。
對應(yīng)網(wǎng)站,JMH Visual Chart(http://deepoove.com/jmh-visual-chart/)惠啄、JMH Visualizer(https://jmh.morethan.io/)慎恒。
展示效果如下圖:
生成 jar 包執(zhí)行
對于大型的測試,一般會放在 Linux 服務(wù)器里去執(zhí)行撵渡。JMH 官方提供了生成 jar 包的方式來執(zhí)行融柬,在 maven 里增加如下插件:
? ? ? ? ? ? org.apache.maven.plugins? ? ? ? maven-shade-plugin? ? ? ? 2.4.1? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? package? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? shade? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jmh-demo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? org.openjdk.jmh.Main? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 復(fù)制代碼
執(zhí)行 maven 的命令生成可執(zhí)行 jar 包,并執(zhí)行:
mvn clean packagejava -jar target/jmh-demo.jar JmhTest復(fù)制代碼
總結(jié)
一篇文章幾乎涵蓋了 JMH 各方面的知識點趋距,如果實踐中還沒運用粒氧,趕緊用起來吧,你的專業(yè)水平將又提升那么一點节腐。當(dāng)然外盯,也可以收藏起來,以備不時不需翼雀。