1. 程式人生 > >【Java虛擬機器】HotSpot即時編譯器優化

【Java虛擬機器】HotSpot即時編譯器優化

HotSpot即時編譯器優化

關於即時編譯器

當虛擬機發現某個方法或程式碼塊的執行特別頻繁時,就會把這些程式碼認定為“熱點程式碼”。

為了提高熱點程式碼的執行效率,在執行時,虛擬機器將會把這些程式碼編譯成與本地平臺相關的機器碼,並進行各種層次的優化,完成這個任務的編譯器稱為即時編譯器(Just In Time Compiler),簡稱JIT編譯器。

熱點探測方法

“熱點程式碼”有兩類:

  • 被多次呼叫的方法
  • 被多次執行的迴圈體

由方法呼叫觸發的編譯,會把整個方法作為編譯物件。

由迴圈體觸發編譯,依然會以整個方法作為編譯物件,這種編譯方式因為編譯發生在執行過程中,因此形象地稱之為棧上替換(On Stack Replacement,簡稱OSR編譯)。

基於取樣的熱點探測

虛擬機器會週期地檢查各個執行緒的棧頂,如果發現某個方法經常出現在棧頂,那這個方法就是“熱點方法”。

基於計數器的熱點探測

虛擬機器會為每個方法建立計數器,統計方法的執行次數,如果執行次數超過一定的閾值就認定它是“熱點方法”。

為每個方法準備了兩類計數器:方法呼叫計數器和回邊計數器。

方法呼叫計數器

具體流程如下圖所示:
在這裡插入圖片描述

回邊計數器

統計一個方法中迴圈體程式碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令稱為“回邊”。

具體流程如下圖所示:
在這裡插入圖片描述

編譯優化技術

公共子表示式消除

如果一個表示式E已經計算過了,並且從先前的計算到現在E中所有的變數都沒有發生變化,那麼E的這次出現就成為了公共子表示式。

對於這種表示式,沒有必要花時間再對它進行計算,只需要直接用前面計算過的表示式結果代替E就可以了。

陣列邊界檢查消除

Java語言訪問陣列元素的時候,系統會自動進行上下界的範圍檢查,也就是說每次陣列元素的讀寫都帶有一次隱含的條件判定操作。

一個常見的情況就是陣列訪問放生在迴圈之中,並且用迴圈變數來進行陣列訪問,如果編譯器只要通過資料流分析就可以判定迴圈變數的取值範圍永遠在區間[0, length)之內,那 在整個迴圈中就可以把陣列的上下界檢查消除。

方法內聯

不僅僅只是把目標方法的程式碼複製到發起呼叫的方法之中,因為Java的許多方法都是在執行時才知道真正呼叫的方法(重寫)

解決方案:

  1. 首先引入“型別繼承關係分析”(Class Hierarchy Analysis,CHA),基於整個應用程式的型別分析技術,用於確定在目前已載入的類中,某個介面是否有多於一種的實現,某個類是否存在子類、子類是否為抽象類等資訊。
  2. 首先向CHA查詢此方法在當前程式下是否有多個目標版本可供選擇,如果只有一個版本,那可以內聯。如果載入了導致繼承關係發生變化的新類,那就需要拋棄已經編譯的程式碼,退回到解釋狀態執行,或重新編譯
  3. 如果想CHA查出有多個版本可供選擇,那麼使用內聯快取完成方法內聯。工作原理大致是:未發生方法呼叫之前,內聯快取是空的,當第一次呼叫發生後,快取記錄下方法接收者的版本資訊,並且每次進行方法呼叫時都比較接受者版本,如果一致,可以一直用下去。如果不一致,取消內聯,進行方法分派。

逃逸分析

當一個物件在方法中被定義後,它可能被外部方法所引用。

例如作為呼叫引數傳遞到其他方法中,稱為方法逃逸。

甚至可能被外部執行緒訪問到,比如賦值為類變數或可以在其他執行緒中訪問的例項變數,稱為執行緒逃逸。

  • 棧上分配:如果一個物件不會逃逸出方法之外,那麼這個物件可以在棧上分配記憶體。
  • 同步消除:如果一個變數不會逃逸出執行緒,那對這個變數實施的同步措施也可以消除掉。
  • 標量替換:把一個Java物件拆散,將其使用到的成員變數恢復原始型別來訪問。

參考

  1. 深入理解Java虛擬機器[書籍]