1、概述:
虛拟機會根據代碼執行情況,如果代碼執行特别頻繁,就将這段代碼編譯成本地平台相關的機器碼,完成這個任務的編譯器就是即時編譯器(Just In Time Compiler)簡稱JIT編譯器,涉及的虛拟機是指HotSpot虛拟機的即時編譯器。
2、HotSpot虛拟機内的即時編譯器
2.1 解釋器與編譯器
解釋器:程式可以迅速啟動和執行,消耗記憶體小 (類似人工 成本地,到後期效率低)
編譯器:随着代碼頻繁執行會執行将代碼編譯成本地機器碼 (類似機器,成本高,到後期效率高)
它們可以進行互補
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9UEVOVTVU5kejpXTmJEViZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TN0ITM0kjM5ETOygDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
當然也可以強制指定
三種模式:
- 1、混合模式(Mixed Mode): 預設模式
- 2、解釋模式(Interpreted Mode): 使用參數-Xint強制
- 3、編譯模式(Compiled Mode): 使用參數-Xcomp
為了解決編譯過程占用程式運作時間的過長,HotSpot虛拟機将會逐漸啟用分層編譯政策。
第0層:程式解釋執行
第1層:C1編譯,(将位元組碼編譯為本地代碼)簡單優化 (編譯速度)
第2層:C2編譯,深度優化,激進優化 (編譯品質)
類似:不斷疊代,由易入難
2.2 編譯對象與觸發條件
如何評判為熱點代碼:
- 被多次調用的方法
- 被多次執行的循環體
第一種:編譯整個方法。
第二種:編譯整個方法,但是編譯發生在執行過程中,是以稱為棧上替換(On Stack Replacement OSR)
問題是多次到底是多少次,用什麼來方法來評判?
熱點探測(Hot Spot Detection):有兩種:
- 1、基于采樣的熱點探測:周期性檢查各個線程的棧頂,來計算那個方法出現次數多。簡單,缺點:受到線程阻塞影響/
- 2、基于計數器的熱點探測:為每個方法增加計數器(方法調用計數器和回邊計數器【表示循環次數】)
HotSpot采用的是第二種
當計數器超過門檻值,就會觸發JIT編譯。
方法調用計數器:
Client模式預設值門檻值為1500次,在Server模式下是10000次。
也可以通過-XX:CompileThreshold 人工設定 ,注意這個計數跟時間有關系的。
(記得玩一款消消樂遊戲,有時間限制,但是如果你消除塊會增加時間,沒有消除它時間會默默減少,直到結束,這個有點類似)
如果超過一定的時間限定,記錄的次數不足觸發編譯。這個方法計數器的次數減少一半(這個稱為熱度衰減)這時期為半衰周期,當然你可以設定沒有衰減,采用絕對次數,通過設定-XX:-UseCounterDecay關閉熱度衰減,-XX:CounterHalfLifeTime 參數設定半衰期周期機關是秒。
回邊計數器:
-XX:BackEdgeThreshold設定門檻值(虛拟機沒有用),-XX:OnStackReplacePercentage來間接調整回邊計數器的門檻值。
Client模式下: 門檻值=方法調用計數器門檻值*OnStackReplacePercentage/100 (OnStackReplacePercentage預設值為933),都取預設值那麼這個門檻值為13995.
Server模式下:門檻值=(方法調用計數器門檻值*OnStackReplacePercentage-InterpreterProfilePercentage)/100,其中OnStackReplacePercentage預設值為140,InterpreterProfilePercentage預設值為33, 結果門檻值為10700
注意:它統計的是絕對次數。
2.3、編譯過程
預設情況下,如果編譯器沒有完成編譯工作,它還是會采用解釋方式繼續執行,而編譯動作會在背景繼續執行,當然你也可以設定參數-XX:-BackgroundCompilation 來禁止背景編譯,當達到JIT要求時候,會等待編譯完再執行(讓我想起小時候,不給我買這個玩具就是不走。O(∩_∩)O哈哈~)
Client Compiler 和Server Compiler 編譯過程不一樣。
Client Compiler: 怎麼快,怎麼來的,順便優化一下。(三段式編譯器)
第一階段:位元組碼構造進階中間代碼表示(HIR),HIR使用靜态單配置設定(SSA)來代表代碼值。
第二階段:後端從HIR中産生低級中間代碼表示(Low-Level Intermediate Representation LIR) 如果空值檢查消除、範圍檢查消除,以便讓HIR達到更高效的代碼形式。
第三階段:後端使用線性掃描算法在LIR上配置設定寄存器,并在LIR上做窺孔優化,然後産生機器代碼。
Server Compiler: 優化到極緻(不惜采取偏激手段)
無用代碼消除、循環展開、循環表達式外提、、、、、
3、編譯優化技術
- 語言無關的經典優化技術之一:公共子表達式消除
- 語言相關的經典優化技術之一:數組範圍檢查消除
- 最重要的優化技術之一:方法内聯
- 最前沿的優化技術之一:逃逸分析
3.1 公共子表達式消除
也就是算出一遍的結果不需要再次重複算一遍 : 分為局部公共子表達式消除和全局公共子表達式消除
例如:
int d = (c*b)*12 + a+(a+b*c);
int d = E*12 + a+(a+E); (其中E=b*c)
int d = E*13 + a*2;
這樣節省時間
3.2 數組邊界檢查消除
編譯器隻要通過資料流分析可以判定循環變量的取值範圍永遠在區間[0,foo.length)之内,那麼整個循環中就可以把數組的上下界檢查消除掉,自動裝箱消除、安全點消除、消除反射
3.3 方法内聯
方法互相調用,優化難度大
3.4 逃逸分析
逃逸分析的基本行為就是分析對象動态作用域,當一個對象在方法裡面被定義後,它可能被外部方法所引用,例如作為調用參數傳遞到其他方法中,這種行為稱為方法逃逸。指派給類變量或可以在其他線程中通路的執行個體變量,這個行為稱為線程逃逸。
如果該方法無法逃逸,可以進行優化。
棧上配置設定(Stack Allocations) :在棧上配置設定記憶體, 随着方法結束而自動銷毀變量。(減少垃圾回收)
同步消除(Synchronization Elimination):不會出現并發的情況(消除同步)
标量替換:資料最小機關例如原始資料類型稱為變量。 可以繼續分解為聚合量,如果這個對象不會逃逸,不會建立對象,而是建立若幹個成員變量來替換。
使用者可以使用參數 -XX:+DoEscapeAnalysis 來手動開啟逃逸分析, 開啟後通過參數: -XX:+PrintEscapeAnalysis來檢視分析結果,使用者可以通過使用參數-XX:EliminateAllocations來開啟标量替換,使用參數+XX:+EliminateLocks來開啟同步消除,使用參數 -XX:+PrintEliminateAllocations來檢視标量的替換情況。