from:http://www.tuicool.com/articles/r6Z7vaj
JVM自動監控這所有方法的執行,如果某個方法是熱點方法,JVM就計劃把該方法的字節碼代碼編譯成本地機器代碼,編譯成機器代碼的過程是在獨立線程中執行的,不會影響程序的執行,這個過程就是JIT(just in time)。
JIT針對下面的幾種方式進行優化
- 把bytecode編譯成本地代碼
- 單態調度(monomorphic dispatch),當個對象的類和其父類間有方法重寫時,JVM調用對象的方法可以通過對象的類型路徑來判斷應該調用父類的方法還是子類的方法,對此JIT進行優化,這種優化是C++所不具備的,C++中需要查找虛函數表。
- 循環展開(loop unrolling)
- 類型銳化
- 逃逸分析(escape analysis)
- 移除無用代碼(這個現在IDE會提示我們的,比如:intellij idea)
- Intrinsics
- 分支預測
- 方法內聯(inlining,對性能的提升很大),默認情況,<= 35字節碼的方法可以進行內聯,通過這個來修改內聯方法的最大值:-XX:MaxInlineSize=,通過-XX:FreqInlineSize=來設置頻繁調用方法的臨界值
這些優化方法通常是層層依賴的,所以當JIT優化后的代碼被JVM應用,就會開始嘗試進行更上一層次的優化。因此我們寫代碼的時候,應該盡量往這些優化方式上面靠。
輸出JIT編譯過的方法
在JVM啟動參數中添加如下的啟動參數:
-XX:+PrintCompilation
輸出內容類似這樣:
31 23 s! sun.misc.URLClassPath::getLoader (136 bytes)
- 第1列 31:為JVM啟動后到該方法被編譯相隔的時間,單位為毫秒
- 第2列 23:編譯ID,用來跟蹤一個方法的編譯、優化、深度優化
- 第3列 s!:s是指該方法是synchronized,感嘆號是指該方法有對異常的處理
- 第4列 sun.misc.URLClassPath::getLoader:被編譯的方法
- 第5列 (136 bytes):方法的字節大小
輸出JIT編譯的細節信息
通過添加參數-XX:+PrintCompilation,可以看到的信息其實并不具體,比如:那些方法進行了內聯,內聯后的二進制代碼是怎么樣的都沒有。而要輸出JIT編譯的細節信息,就需要在JVM啟動參數中添加這個參數:
-XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+PrintAssembly
輸出的編譯信息,默認情況是在啟動JVM的目錄下一個名為:hotspot_pid<PID>.log的文件
如果想指定文件路徑和文件名的話,可以再添加一個啟動參數:
-XX:LogFile=<pathto file>
輸出的是一個很大的xml文件,可能有幾百兆,內容大致如下:
<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'/>
而且內容很難讀懂,建議使用JITWatch( https://github.com/AdoptOpenJDK/jitwatch/ )的可視化界面來查看JIT編譯的細節信息。同時JITWatch還可以給出很多優化建議,給我們有效的優化代碼提供參考,詳見下文。
JIT編譯模式
C1: 通常用于那種快速啟動的GUI應用,對應啟動參數:-client
C2: 通常用于長時間允許的服務端應用,對應啟動參數:-server
分層編譯模式(tiered compilation):這是自從Java SE 7以后的新特性,可通過添加啟動參數來開啟:
-XX:+TieredCompilation
這個特性在應用啟動階段使用C1模式以達到快速啟動的效果,一旦應用程序運行起來以后,C2模式將取代C1模式,以進行更深度的優化。在Java SE 8中,這個特性是默認的。
JITWatch
前面也提到了,JITWatch可以通過可視化界面來幫助我們分析JVM輸出的JIT編譯輸出日志,還可以幫助我們靜態分析jar中的代碼是否符合JIT編譯優化的條件,還可以以曲線圖形的方式展示JIT編譯的整個過程中的一些指標,非常好用的工具。
下載
JITWatch需要在github上把代碼clone下來,然后用maven來運行,地址為: https://github.com/AdoptOpenJDK/jitwatch/
運行JITWwatch
在代碼根目錄下執行 launchUI.sh(
Linux/Mac)或則 launchUI.bat(windows)
如果你使用maven,也可以在代碼根目錄下這樣運行(其他運行方式,請參考JITWatch的github首頁)
mvncleancompileexec:java
如果你使用的是mac,而且idk版本是jdk7,且運行mvn clean compile exec:java時出現下面的錯誤和異常時:
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]
請在org.adoptopenjdk.jitwatch.launch.LaunchUI類的main函數開頭處添加下面的代碼(或者直接使用我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[] {}); }
然后重新運行即可看到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