from:http://www.tuicool.com/articles/r6Z7vaj

JVM自動(dòng)監(jiān)控這所有方法的執(zhí)行,如果某個(gè)方法是熱點(diǎn)方法,JVM就計(jì)劃把該方法的字節(jié)碼代碼編譯成本地機(jī)器代碼,編譯成機(jī)器代碼的過程是在獨(dú)立線程中執(zhí)行的,不會(huì)影響程序的執(zhí)行,這個(gè)過程就是JIT(just in time)。

JIT針對下面的幾種方式進(jìn)行優(yōu)化

  • 把bytecode編譯成本地代碼
  • 單態(tài)調(diào)度(monomorphic dispatch),當(dāng)個(gè)對象的類和其父類間有方法重寫時(shí),JVM調(diào)用對象的方法可以通過對象的類型路徑來判斷應(yīng)該調(diào)用父類的方法還是子類的方法,對此JIT進(jìn)行優(yōu)化,這種優(yōu)化是C++所不具備的,C++中需要查找虛函數(shù)表。
  • 循環(huán)展開(loop unrolling)
  • 類型銳化
  • 逃逸分析(escape analysis)
  • 移除無用代碼(這個(gè)現(xiàn)在IDE會(huì)提示我們的,比如:intellij idea)
  • Intrinsics
  • 分支預(yù)測
  • 方法內(nèi)聯(lián)(inlining,對性能的提升很大),默認(rèn)情況,<= 35字節(jié)碼的方法可以進(jìn)行內(nèi)聯(lián),通過這個(gè)來修改內(nèi)聯(lián)方法的最大值:-XX:MaxInlineSize=,通過-XX:FreqInlineSize=來設(shè)置頻繁調(diào)用方法的臨界值

這些優(yōu)化方法通常是層層依賴的,所以當(dāng)JIT優(yōu)化后的代碼被JVM應(yīng)用,就會(huì)開始嘗試進(jìn)行更上一層次的優(yōu)化。因此我們寫代碼的時(shí)候,應(yīng)該盡量往這些優(yōu)化方式上面靠。

輸出JIT編譯過的方法

在JVM啟動(dòng)參數(shù)中添加如下的啟動(dòng)參數(shù):

-XX:+PrintCompilation 

輸出內(nèi)容類似這樣:

31    23 s!    sun.misc.URLClassPath::getLoader (136 bytes) 
  • 第1列  31:為JVM啟動(dòng)后到該方法被編譯相隔的時(shí)間,單位為毫秒
  • 第2列  23:編譯ID,用來跟蹤一個(gè)方法的編譯、優(yōu)化、深度優(yōu)化
  • 第3列  s!:s是指該方法是synchronized,感嘆號(hào)是指該方法有對異常的處理
  • 第4列  sun.misc.URLClassPath::getLoader:被編譯的方法
  • 第5列  (136 bytes):方法的字節(jié)大小

輸出JIT編譯的細(xì)節(jié)信息

通過添加參數(shù)-XX:+PrintCompilation,可以看到的信息其實(shí)并不具體,比如:那些方法進(jìn)行了內(nèi)聯(lián),內(nèi)聯(lián)后的二進(jìn)制代碼是怎么樣的都沒有。而要輸出JIT編譯的細(xì)節(jié)信息,就需要在JVM啟動(dòng)參數(shù)中添加這個(gè)參數(shù):

-XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+PrintAssembly 

輸出的編譯信息,默認(rèn)情況是在啟動(dòng)JVM的目錄下一個(gè)名為:hotspot_pid<PID>.log的文件

如果想指定文件路徑和文件名的話,可以再添加一個(gè)啟動(dòng)參數(shù):

-XX:LogFile=<pathto file> 

輸出的是一個(gè)很大的xml文件,可能有幾百兆,內(nèi)容大致如下:

<nmethodcompile_id='2' compiler='C1' level='3'  entry='0x00000001023fe240' size='1224'  address='0x00000001023fe0d0' relocation_offset='288'  insts_offset='368' stub_offset='880' scopes_data_offset='1032'  scopes_pcs_offset='1104' dependencies_offset='1200'  nul_chk_table_offset='1208'  method='java/lang/String hashCode ()I' bytes='55' count='512'  backedge_count='8218' iicount='512' stamp='0.350'/> 

而且內(nèi)容很難讀懂,建議使用JITWatch( https://github.com/AdoptOpenJDK/jitwatch/ )的可視化界面來查看JIT編譯的細(xì)節(jié)信息。同時(shí)JITWatch還可以給出很多優(yōu)化建議,給我們有效的優(yōu)化代碼提供參考,詳見下文。

JIT編譯模式

C1: 通常用于那種快速啟動(dòng)的GUI應(yīng)用,對應(yīng)啟動(dòng)參數(shù):-client

C2: 通常用于長時(shí)間允許的服務(wù)端應(yīng)用,對應(yīng)啟動(dòng)參數(shù):-server

分層編譯模式(tiered compilation):這是自從Java SE 7以后的新特性,可通過添加啟動(dòng)參數(shù)來開啟:

-XX:+TieredCompilation 

這個(gè)特性在應(yīng)用啟動(dòng)階段使用C1模式以達(dá)到快速啟動(dòng)的效果,一旦應(yīng)用程序運(yùn)行起來以后,C2模式將取代C1模式,以進(jìn)行更深度的優(yōu)化。在Java SE 8中,這個(gè)特性是默認(rèn)的。

JITWatch

前面也提到了,JITWatch可以通過可視化界面來幫助我們分析JVM輸出的JIT編譯輸出日志,還可以幫助我們靜態(tài)分析jar中的代碼是否符合JIT編譯優(yōu)化的條件,還可以以曲線圖形的方式展示JIT編譯的整個(gè)過程中的一些指標(biāo),非常好用的工具。

下載

JITWatch需要在github上把代碼clone下來,然后用maven來運(yùn)行,地址為: https://github.com/AdoptOpenJDK/jitwatch/

運(yùn)行JITWwatch

在代碼根目錄下執(zhí)行 launchUI.sh( Linux/Mac)或則 launchUI.bat(windows)

如果你使用maven,也可以在代碼根目錄下這樣運(yùn)行(其他運(yùn)行方式,請參考JITWatch的github首頁)

mvncleancompileexec:java 

如果你使用的是mac,而且idk版本是jdk7,且運(yùn)行mvn clean compile exec:java時(shí)出現(xiàn)下面的錯(cuò)誤和異常時(shí): 

Causedby: java.lang.NullPointerException  atcom.sun.t2k.MacFontFinder.initPSFontNameToPathMap(MacFontFinder.java:339)  atcom.sun.t2k.MacFontFinder.getFontNamesOfFontFamily(MacFontFinder.java:390)  atcom.sun.t2k.T2KFontFactory.getFontResource(T2KFontFactory.java:233)  atcom.sun.t2k.LogicalFont.getSlot0Resource(LogicalFont.java:184)  atcom.sun.t2k.LogicalFont.getSlotResource(LogicalFont.java:228)  atcom.sun.t2k.CompositeStrike.getStrikeSlot(CompositeStrike.java:86)  atcom.sun.t2k.CompositeStrike.getMetrics(CompositeStrike.java:132)  atcom.sun.javafx.font.PrismFontUtils.getFontMetrics(PrismFontUtils.java:31)  atcom.sun.javafx.font.PrismFontLoader.getFontMetrics(PrismFontLoader.java:466)  atjavafx.scene.text.Text.<init>(Text.java:153)  atcom.sun.javafx.scene.control.skin.Utils.<clinit>(Utils.java:52)  ... 13 more   [ERROR] Failedto executegoalorg.codehaus.mojo:exec-maven-plugin:1.5.0:java (default-cli) onprojectjitwatch-ui: Anexceptionoccuredwhile executingtheJavaclass. null: InvocationTargetException: Exceptionin Applicationstartmethod: ExceptionInInitializerError: NullPointerException -> [Help 1] 

請?jiān)趏rg.adoptopenjdk.jitwatch.launch.LaunchUI類的main函數(shù)開頭處添加下面的代碼(或者直接使用我fork修改好的 JITWatch ):

final Class<?> macFontFinderClass = Class.forName("com.sun.t2k.MacFontFinder"); final java.lang.reflect.FieldpsNameToPathMap = macFontFinderClass.getDeclaredField("psNameToPathMap"); psNameToPathMap.setAccessible(true); if (psNameToPathMap.get(null) == null) {     psNameToPathMap.set(         null, new java.util.HashMap<String, String>()); } final java.lang.reflect.FieldallAvailableFontFamilies = macFontFinderClass.getDeclaredField("allAvailableFontFamilies"); allAvailableFontFamilies.setAccessible(true); if (allAvailableFontFamilies.get(null) == null) {     allAvailableFontFamilies.set(         null, new String[] {}); } 

然后重新運(yùn)行即可看到JITWatch的界面。

Reference

http://www.oracle.com/technetwork/articles/java/architect-evans-pt1-2266278.html

https://www.chrisnewland.com/images/jitwatch/HotSpot_Profiling_Using_JITWatch.pdf