你的Java代碼對JIT編譯友好么县袱?

JIT編譯器是Java虛擬機(以下簡稱JVM)中效率最高并且最重要的組成部分之一廷粒。但是很多的程序并沒有充分利用JIT的高性能優(yōu)化能力戳葵,很多開發(fā)者甚至也并不清楚他們的程序有效利用JIT的程度。

在本文中泉懦,我們將介紹一些簡單的方法來驗證你的程序是否對JIT友好稿黍。這里我們并不打算覆蓋諸如JIT編譯器工作原理這些細節(jié)。只是提供一些簡單基礎(chǔ)的檢測和方法來幫助你的代碼對JIT友好崩哩,進而得到優(yōu)化巡球。

JIT編譯的關(guān)鍵一點就是JVM會自動地監(jiān)控正在被解釋器執(zhí)行的方法。一旦某個方法被視為頻繁調(diào)用邓嘹,這個方法就會被標記酣栈,進而編譯成本地機器指令。這些頻繁執(zhí)行的方法的編譯由后臺的一個JVM線程來完成汹押。在編譯完成之前矿筝,JVM會執(zhí)行這個方法的解釋執(zhí)行版本。一旦該方法編譯完成棚贾,JVM會使用將方法調(diào)度表中該方法的解釋的版本替換成編譯后的版本跋涣。

Hotspot虛擬機有很多JIT編譯優(yōu)化的技術(shù),但是其中最重要的一個優(yōu)化技術(shù)就是內(nèi)聯(lián)鸟悴。在內(nèi)聯(lián)的過程中陈辱,JIT編譯器有效地將一個方法的方法體提取到其調(diào)用者中,從而減少虛方法調(diào)用细诸。舉個例子沛贪,

publicintadd(intx,inty) {returnx + y;}intresult = add(a, b);

當內(nèi)聯(lián)發(fā)生之后,上述代碼會變成

int result=a + b;

上面的變量a和b替換了方法的參數(shù)震贵,并且add方法的方法體已經(jīng)復(fù)制到了調(diào)用者的區(qū)域利赋。使用內(nèi)聯(lián)可以為程序帶來很多好處,比如

不會引起額外的性能損失

減少指針的間接引用

不需要對內(nèi)聯(lián)方法進行虛方法查找

另外猩系,通過將方法的實現(xiàn)復(fù)制到調(diào)用者中媚送,JIT編譯器處理的代碼增多,使得后續(xù)的優(yōu)化和更多的內(nèi)聯(lián)成為可能寇甸。

內(nèi)聯(lián)取決于方法的大小塘偎。缺省情況下疗涉,含有35個字節(jié)碼或更少的方法可以進行內(nèi)聯(lián)操作。對于被頻繁調(diào)用的方法吟秩,臨界值可以達到325個字節(jié)咱扣。我們可以通過設(shè)置-XX:MaxInlineSize=#

選項來修改最大的臨界值,通過設(shè)置?XX:FreqInlineSize=#選項來修改頻繁調(diào)用的方法的臨界值涵防。但是在沒有正確的分析的情況下闹伪,我們不應(yīng)該修改這些配置。因為盲目地修改可能會對程序的性能帶來不可預(yù)料的影響壮池。

由于內(nèi)聯(lián)會對代碼的性能有大幅提升偏瓤,因此讓盡可能多的方法達到內(nèi)聯(lián)條件尤為重要。這里我們介紹一款叫做Jarscan的工具來幫助我們檢測程序中有多少方法是對內(nèi)聯(lián)友好的椰憋。

Jarscan工具是分析JIT編譯的JITWatch開源工具套件中的一部分厅克。和在運行時分析JIT日志的主工具不同,Jarscan是一款靜態(tài)分析jar文件的工具熏矿。該工具的輸出結(jié)果格式為CSV已骇,結(jié)果中包含了超過頻繁調(diào)用方法臨界值的方法等信息离钝。JITWatch和Jarscan是AdoptOpenJDK工程的一部分票编,該工程由Chris

Newland領(lǐng)導(dǎo)。

在使用Jarscan并得到分析結(jié)果之前卵渴,需要從AdoptOpenJDK Jenkins網(wǎng)站下載二進制工具

7 工具

運行很簡單慧域,如下所示

./jarScan.sh

上面產(chǎn)生的報告對于開發(fā)團隊的開發(fā)工作很有幫助,根據(jù)報告結(jié)果浪读,他們可以查找程序中是否包含了過大而不能JIT編譯的關(guān)鍵路徑方法昔榴。上面的操作依賴于手動執(zhí)行。但是為了以后的自動化碘橘,可以開啟Java的-XX:+PrintCompilation

選項互订。開啟這個選項會生成如下的日志信息:

371java.lang.String::hashCode(67bytes)1242s!java.lang.ClassLoader::loadClass(58bytes)

其中,第一列表示從進程啟動到JIT編譯發(fā)生經(jīng)過的時間痘拆,單位為毫秒仰禽。第二列表示的是編譯id,表明該方法正在被編譯(在Hotspot中一個方法可以多次去優(yōu)化和再優(yōu)化)纺蛆。第三列表示的是附加的一些標志信息吐葵,比如s代表synchronized,桥氏!代表有異常處理温峭。最后兩列分別代表正在編譯的方法名稱和該方法的字節(jié)大小。

關(guān)于PrintCompilation輸出的更多細節(jié)字支,Stephen Colebourne寫過一篇博客文章詳細介紹日志結(jié)果中各列的具體含義凤藏,感興趣的可以訪問這里閱讀奸忽。

PrintCompilation的輸出結(jié)果會提供運行時正在編譯的方法的信息,Jarscan工具的輸出結(jié)果可以告訴我們哪些方法不能進行JIT編譯清笨。結(jié)合兩者月杉,我們就可以清楚地知道哪些方法進行了編譯,哪些沒有進行抠艾。另外苛萎,PrintCompilation選項可以在線上環(huán)境使用,因為開啟這個選項幾乎不會影響JIT編譯器的性能检号。

但是腌歉,PrintCompilation也存在著兩個小問題,有時候會顯得不是那么方便:

輸出的結(jié)果中未包含方法的簽名齐苛,如果存在重載方法翘盖,區(qū)分起來則比較困難。

Hotspot虛擬機目前不能將結(jié)果輸出到單獨的文件中凹蜂,目前只能是以標準輸出的形式展示馍驯。

上述的第二個問題的影響在于PrintCompilation的日志會和其他常用的日志混在一起。對于大多數(shù)服務(wù)器端程序來說玛痊,我們需要一個過濾進程來將PrintCompilation的日志過濾到一個獨立的日志中汰瘫。最簡單的判斷一個方法否是JIT友好的途徑就是遵循下面這個簡單的步驟:

確定程序中位于要處理的關(guān)鍵路徑上的方法。

檢查這些方法沒有出現(xiàn)在Jarscan的輸出結(jié)果中擂煞。

檢查這些方法確實出現(xiàn)在了PrintCompilation的輸出結(jié)果中混弥。

如果一個方法超過了內(nèi)聯(lián)的臨界值,大多數(shù)情況下最常用的方法就是講這個重要的方法拆分成多個可以進行內(nèi)聯(lián)的小方法对省,這樣修改之后通常會獲取更好的執(zhí)行效率蝗拿。但是對于所有的性能優(yōu)化而言,優(yōu)化之前的執(zhí)行效率需要測量記錄蒿涎,并且需要需要同優(yōu)化后的數(shù)據(jù)進行對比之后哀托,才能決定是否進行優(yōu)化。為了性能優(yōu)化而做出的改變不應(yīng)該是盲目的劳秋。

幾乎所有的Java程序都依賴大量的提供關(guān)鍵功能的庫仓手。Jarscan可以幫助我們檢測哪些庫或者框架的方法超過了內(nèi)聯(lián)的臨界值。舉一個具體的例子俗批,我們這里檢查JVM主要的運行時庫

rt.jar文件俗或。

為了讓結(jié)果有點意思,我們分別比較Java 7 和Java 8岁忘,并查看這個庫的變化辛慰。在開始之前我們需要安裝Java 7 和 Java8

JDK。首先干像,我們分別運行Jarscan掃描各自的rt.jar文件帅腌,并得到用來后續(xù)分析的報告結(jié)果:

$ ./jarScan.sh/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/jre/lib/rt.jar> large_jre_methods_7u71.txt$ ./jarScan.sh/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/rt.jar> large_jre_methods_8u25.txt

上述操作結(jié)束之后驰弄,我們得到兩個CSV文件,一個是JDK 7u71的結(jié)果速客,另一個是JDK

8u25戚篙。然后我們看一看不同的版本內(nèi)聯(lián)情況有哪些變化。首先溺职,一個最簡單的判斷驗證方式岔擂,看一看不同版本的JRE中有多少對JIT不友好的方法。

$ wc -l large_jre_methods_*3684large_jre_methods_7u71.txt3576large_jre_methods_8u25.txt

我們可以看到浪耘,相比Java 7乱灵,Java 8

少了100多個內(nèi)聯(lián)不友好的方法。下面繼續(xù)深入研究七冲,看看一些關(guān)鍵的包的變化痛倚。為了便于理解如何操作,我們再次介紹一下Jarscan的輸出結(jié)果澜躺。Jarscan的輸出結(jié)果有如下3個屬性組成:

"","",

了解了上述的格式蝉稳,我們可以利用一些Unix文本處理的工具來研究報告結(jié)果。比如掘鄙,我們想看一下Java 7 和 Java 8

這兩個版本中java.lang包下哪些方法變得內(nèi)聯(lián)友好了:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt| grep -i^\"java.lang | sort | uniq -c

上面的語句使用grep命令過濾出每份報告中以java.lang開頭的行耘戚,即只顯示位于包java.lang中的類的內(nèi)聯(lián)不友好的方法。sort | uniq

-c

是一個比較老的Unix小技巧通铲,首先將講行信息進行排序(相同的信息將聚集到一起)毕莱,然后對上面的排序數(shù)據(jù)進行去重操作器贩。另外本命令還會統(tǒng)計一個當前行信息重復(fù)的次數(shù)颅夺,這個數(shù)據(jù)位于每一行信息的最開始部分。讓我們看一下上述命令的執(zhí)行結(jié)果:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt | grep -i ^\"java.lang | sort | uniq -c

2 "java.lang.CharacterData00","intgetNumericValue(int)",835

2 "java.lang.CharacterData00","inttoLowerCase(int)",1339

2 "java.lang.CharacterData00","inttoUpperCase(int)",1307

// ... skipped output

2 "java.lang.invoke.DirectMethodHandle","privatestaticjava.lang.invoke.LambdaForm makePreparedLambdaForm(java.lang.invoke.MethodType,int)",613

1 "java.lang.invoke.InnerClassLambdaMetafactory","privatejava.lang.Class spinInnerClass()",497

// ... more output ----

報告中蛹稍,以2(這是使用了uniq -c 對相同的信息計算數(shù)量的結(jié)果)最為起始的條目說明這些方法在Java 7 和Java 8

中起字節(jié)碼大小沒有改變吧黄。雖然這并不能完全肯定地說明這些方法的字節(jié)碼沒有改變,但通常我們也可以視為沒有改變唆姐。重復(fù)次數(shù)為1的方法有如下的情況:

a)方法的字節(jié)碼已經(jīng)改變拗慨。

b)這些方法為新的方法。

我們看一下以1開始的行數(shù)據(jù)

1"java.lang.invoke.AbstractValidatingLambdaMetafactory","voidvalidateMetafactoryArgs()",8641"java.lang.invoke.InnerClassLambdaMetafactory","privatejava.lang.Class spinInnerClass()",4971"java.lang.reflect.Executable","java.lang.StringsharedToGenericString(int,boolean)",329

上面三個對內(nèi)聯(lián)不友好的方法全部來自Java

8奉芦,因此這屬于新方法的情況赵抢。前兩個方法與lamda表達式實現(xiàn)相關(guān),第三個方法和反射子系統(tǒng)中繼承層級調(diào)整有關(guān)声功。在這里烦却,這個改變就是在Java 8

中引入了方法和構(gòu)造器可以繼承的通用基類。

最后先巴,我們看一看JDK核心庫一些令人驚訝的特性:

$ grep -i ^\"java.lang.String large_jre_methods_8u25.txt

"java.lang.String","publicjava.lang.String[] split(java.lang.String,int)",326

"java.lang.String","publicjava.lang.String toLowerCase(java.util.Locale)",431

"java.lang.String","publicjava.lang.String toUpperCase(java.util.Locale)",439

從上面的日志我們可以了解到其爵,即使是Java 8

中一些java.lang.String中一些關(guān)鍵的方法還是處于內(nèi)聯(lián)不友好的狀態(tài)冒冬。尤其是toLowerCase和toUpperCase這兩個方法居然過大而無法內(nèi)聯(lián),著實讓人感到奇怪摩渺。但是简烤,這兩個方法由于要處理UTF-8數(shù)據(jù)而不是簡單的ASCII數(shù)據(jù),進而增加了方法的復(fù)雜性和大小摇幻,因而超過了內(nèi)聯(lián)友好的臨界值横侦。

對于性能要求較高并且確定只處理ASCII數(shù)據(jù)的程序,通常我們需要實現(xiàn)一個自己的StringUtils類绰姻。該類中包含一些靜態(tài)的方法來實現(xiàn)上述內(nèi)聯(lián)不友好的方法的功能丈咐,但這些靜態(tài)方法既保持緊湊型又能到達內(nèi)聯(lián)的要求。

上述我們討論的改進都是大部分基于靜態(tài)分析龙宏。除此之外棵逊,使用強大的JITWatch工具可以幫助我們更好地優(yōu)化。JITWatch工具需要設(shè)置-XX:+LogCompilation選項開啟日志打印银酗。其打印出來的日志為XML格式辆影,而非PrintCompilation簡單的文本輸出,并且這些日志比較大黍特,通常會到達幾百MB蛙讥。它會影響正在運行的程序(默認情況下主要來自日志輸出的影響),因此這個選項不適合在線上的生產(chǎn)環(huán)境使用灭衷。

PrintCompilation和Jarscan結(jié)合使用并不困難次慢,但卻提供了簡單且很有實際作用的一步,尤其是對于開發(fā)團隊打算研究其程序中即時編譯執(zhí)行情況時翔曲。大多數(shù)情況下迫像,在性能優(yōu)化中,一個快速的分析可以幫助我們完成一些容易實現(xiàn)的目標瞳遍。

關(guān)于作者

Ben Evans是jClarity公司的CEO闻妓,jClarity是一家致力于Java和JVM性能分析研究的創(chuàng)業(yè)公司。除此之外他還是London Java

Community的負責(zé)人之一并在Java Community Process Executive Committee有一席之地掠械。他之前的項目有Google

IPO性能測試由缆,金融交易系統(tǒng),90年代知名電影網(wǎng)站等猾蒂。

1均唉、具有1-5工作經(jīng)驗的,面對目前流行的技術(shù)不知從何下手肚菠,需要突破技術(shù)瓶頸的可以加群舔箭。

2、在公司待久了案糙,過得很安逸限嫌,但跳槽時面試碰壁靴庆。需要在短時間內(nèi)進修、跳槽拿高薪的可以加群怒医。

3炉抒、如果沒有工作經(jīng)驗,但基礎(chǔ)非常扎實稚叹,對java工作機制焰薄,常用設(shè)計思想,常用java開發(fā)框架掌握熟練的扒袖,可以加群塞茅。

4、覺得自己很牛B季率,一般需求都能搞定野瘦。但是所學(xué)的知識點沒有系統(tǒng)化,很難在技術(shù)領(lǐng)域繼續(xù)突破的可以加群飒泻。

5.群號656060703java高級開發(fā)群

6.阿里Java高級大牛直播講解知識點鞭光,分享知識,上面五大專題都是各位老師多年工作經(jīng)驗的梳理和總結(jié)泞遗,帶著大家全面惰许、科學(xué)地建立自己的技術(shù)體系和技術(shù)認知!

查看英文原文:Is??Your Java Application Hostile to JIT Compilation?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末史辙,一起剝皮案震驚了整個濱河市汹买,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聊倔,老刑警劉巖晦毙,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異方库,居然都是意外死亡结序,警方通過查閱死者的電腦和手機障斋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門纵潦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垃环,你說我怎么就攤上這事邀层。” “怎么了遂庄?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵寥院,是天一觀的道長。 經(jīng)常有香客問我涛目,道長秸谢,這世上最難降的妖魔是什么凛澎? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮估蹄,結(jié)果婚禮上塑煎,老公的妹妹穿的比我還像新娘。我一直安慰自己臭蚁,他們只是感情好最铁,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垮兑,像睡著了一般冷尉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上系枪,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天雀哨,我揣著相機與錄音,去河邊找鬼私爷。 笑死震束,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的当犯。 我是一名探鬼主播垢村,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嚎卫!你這毒婦竟也來了嘉栓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤拓诸,失蹤者是張志新(化名)和其女友劉穎侵佃,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奠支,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡馋辈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倍谜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迈螟。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尔崔,靈堂內(nèi)的尸體忽然破棺而出答毫,到底是詐尸還是另有隱情,我是刑警寧澤季春,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布洗搂,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耘拇。R本人自食惡果不足惜撵颊,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惫叛。 院中可真熱鬧秦驯,春花似錦、人聲如沸挣棕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洛心。三九已至固耘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間词身,已是汗流浹背厅目。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留法严,地道東北人损敷。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像深啤,于是被迫代替她去往敵國和親拗馒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法溯街,類相關(guān)的語法诱桂,內(nèi)部類的語法,繼承相關(guān)的語法呈昔,異常的語法挥等,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 兩年前阿里開源了Dexposed 項目,它能夠在Dalvik上無侵入地實現(xiàn)運行時方法攔截堤尾,正如其介紹「enable...
    weishu閱讀 4,562評論 3 42
  • 一:java概述:1肝劲,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境郭宝,java的開發(fā)工...
    ZaneInTheSun閱讀 2,629評論 0 11
  • 《深入理解Java虛擬機》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分辞槐。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,062評論 1 34
  • 外面太陽挺好。車間里剩蟀,有人在放肯尼基的薩克斯曲催蝗,回家。 挺意外的育特,因為他們經(jīng)常放的,都是流行傷感類的歌曲。放純音樂...
    上林葉閱讀 411評論 1 0