更多 Java 虛擬機方面的文章戏罢,請參見文集《Java 虛擬機》
前端編譯器 VS 后端編譯器
-
前端編譯器:
javac
編譯屋谭,在程序運行前,將 源文件 轉(zhuǎn)化為 字節(jié)碼 即 .class 文件- Java 程序最初只能通過解釋器解釋執(zhí)行龟糕,即 JVM 對字節(jié)碼逐條解釋執(zhí)行桐磁,因此執(zhí)行速度比較慢。
- 字節(jié)碼與平臺無關(guān)
-
后端編譯器:JIT 編譯讲岁,在程序運行期間我擂,將 字節(jié)碼 轉(zhuǎn)化為 機器碼
- 機器碼與平臺相關(guān)
JIT 即時編譯
當 JVM 發(fā)現(xiàn)某個方法或代碼塊執(zhí)行特別頻繁時,就將其認定為 熱點代碼(Hot Spot Code)缓艳。在程序運行期間校摩,JVM 將這些熱點代碼編譯為與本地平臺相關(guān)的機器碼,并進行各層次的優(yōu)化阶淘,從而提升熱點代碼的執(zhí)行效率秧耗。
基本流程如下:
JIT 即時編譯
如何檢測熱點代碼(Hot Spot Code):
- 基于 采樣 的熱點檢測:檢查各個線程的棧頂。
- 基于 計數(shù)器 的熱點檢測:HotSpot 虛擬機采用:
- 方法計數(shù)器:統(tǒng)計每個方法調(diào)用的次數(shù)
- 回邊計數(shù)器:統(tǒng)計每個方法中循環(huán)體代碼執(zhí)行的次數(shù)
即時編譯的設(shè)置及優(yōu)化
初級調(diào)優(yōu)
HotSpot 虛擬機內(nèi)置兩個 JIT 編譯器:
-
客戶模式 Client Compiler舶治,即 C1 編譯
- 無采樣分井,立即 JIT 編譯,輕量優(yōu)化
- JIT 編譯的類較多霉猛,可能導致代碼緩存不夠用
- 速度較快尺锚,適用于短暫的應用程序
-
服務(wù)器模式 Server Compiler,即 C2 編譯
- 采集 一萬次 調(diào)用樣本后深度編譯優(yōu)化
- JIT 編譯的類較少
- 啟動速度較慢惜浅,運行起來后性能逐步提升
- 每次 GC瘫辩,計數(shù)器衰減一半
- 可以設(shè)置
-XX:-UseCounterDelay
來禁止衰減
Java 8 支持多層編譯,即程序啟動時使用 C1 編譯坛悉,樣本足夠后使用 C2 編譯:
- 禁止多層編譯:
-XX:-TieredCompilation
- 啟用多層編譯:
-XX:+TieredCompilation
優(yōu)化代碼緩存
如果緩存過小伐厌,有些熱點代碼可能不會被 JIT 編譯。
C1 編譯的類較多裸影,可能導致代碼緩存不夠用挣轨。
設(shè)置代碼緩存大小:-XX:ReservedCodeCacheSize = 32m
編譯閾值
即計數(shù)器的閾值轩猩,默認為10000卷扮,即方法計數(shù)器和回邊計數(shù)器的總和達到了10000就觸發(fā) JIT 編譯。
設(shè)置編譯閾值:-XX:CompileThreshold = 10000
內(nèi)聯(lián) Inline
將方法的代碼復制到發(fā)起調(diào)用的方法里均践,以消除方法調(diào)用晤锹。
因為調(diào)用一個小方法可能比直接執(zhí)行該小方法對應的代碼更耗時。
-
-XX:MaxInlineSize=35byte
:能被內(nèi)聯(lián)的方法最大字節(jié)碼大小 -
-XX:FreqInlineSize=325byte
:頻繁調(diào)用的方法能被內(nèi)聯(lián)的最大字節(jié)碼大小
如何讓方法更容易被內(nèi)聯(lián):拆分不常訪問的路徑彤委。例如:
public void f() {
if(most case) {
...
}
else {
... // 將不常訪問的路徑的代碼拆分到函數(shù) g() 中
... // 以降低整體代碼的大小鞭铆,使得 most case 中的代碼可以被內(nèi)聯(lián)
}
}
打印 JIT 編譯信息
java -XX:PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:PrintInline > a.out