JIT編譯器

爲什麼會Java虛擬機會同一時候存在解釋器和編譯器呢?

這是爲了兼顧啓動效率和運行效率兩個方面。Java程序最初是通過解釋器進行解釋運行的,當虛擬機返現某個方法或代碼塊的運行特別頻繁時,就會把這段代碼標記爲熱點代碼,爲了提供熱點代碼的運行效率,在運行時,虛擬機就會把這些代碼編譯成與本地平臺相關的機器碼。並進行各種層次的優化。

 

當編譯器做的激進優化不成立,不如載入了新類後類型繼承結構出現變化。出現了罕見陷阱時能夠進行逆優化退回到解釋狀態繼續運行。

 

以上描寫敘述的兩種配合關係例如以下圖所看到的:

這裏寫圖片描寫敘述

一 編譯模式

HotSpot JVM內置了兩個編譯器,各自是Client Complier和Server Complier,虛擬機默認是Client模式。我們也能夠通過

  • -client:強制虛擬機運行Client模式
  • -server:強制虛擬機運行Server模式

而不管是Client模式還是Server模式,虛擬機都會運行在解釋器和編譯器配合使用的混合模式下。能夠通過

  • -Xint:強制虛擬機運行於解釋模式
  • -Xcomp:強制虛擬機運行於編譯模式

以上描寫敘述的運行模式例如以下圖所看到的:

這裏寫圖片描寫敘述

二 分層編譯

爲什麼會存在分層編譯?

這是由於編譯器編譯本機代碼須要佔用程序運行時間,要編譯出優化程度更高的代碼鎖花費的時間可能更長,並且想要編譯出優化程度更高的代碼,解釋器可能還要替編譯器收集性能監控信息。這對解釋運行的速度也有影響。爲了在程序啓動響應速度和運行效率之間尋找平衡點。因此採用分層編譯的策略。

分層策略例如以下所看到的:

  • 第0層:程序解釋運行。解釋器不開啓性能監控功能,可觸發第1層編譯。
  • 第1層:即C1編譯。將字節碼編譯爲本地代碼。進行簡單和可靠的優化,如有必要將增加性能監控的邏輯。
  • 第2層:即C2編譯,將字節碼編譯爲本地代碼,同一時候啓用一些編譯耗時較長的優化,甚至會依據性能監控信息進行一些不可靠的激進優化。

     

     

三 編譯對象

編譯對象即爲會被編譯優化的熱點代碼。有下面兩類:

  • 被多次調用的方法
  • 被多次運行的循環體

四 觸發條件

上面描寫敘述中使用多次這個概念,那麼什麼算多次呢?

這就牽扯到觸發條件這個概念,推斷一段代碼是否是熱點代碼。是否須要觸發即時編譯,這樣的行爲成爲熱點探測(Spot Dectection)。

 

熱點探測有兩種手段:

4.1 基於採樣的熱點探測(Sample Based Hot Spot Dectection)

虛擬機會週期性的檢查各個線程的棧頂,假設發現某些方法常常性的出如今棧頂,那麼這種方法就是熱點方法。

4.2 基於計數器的熱點探測(Counter Based Hot Spot Dectection)

虛擬機會爲每一個方法或代碼塊建立計數器,統計方法的運行次數。假設運行次數超過一定的閾值就覺得他是熱點方法。

HotSpot JVM使用另外一種方法基於計數器的熱點探測方法。它爲每一個方法準備了兩類計數器:

4.2.1 方法調用計數器

這個閾值在Client模式下是1500次。在Server模式下是10000此,這個閾值能夠通過參數-XX:CompileThreadhold來人爲設定。

假設不做不論什麼設置。方法調用次數統計的並非方法被調用的絕對次數,而是相對的運行頻率,即一段時間內方法被調用的次數,當超過一定時間限度,假設方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那這種方法的調用計數器會被降低一半,這個過程被稱爲方法調用計數器的熱度衰減(Counter Decay)。而這段時間就稱爲此方法統計的半衰週期(Counter Half Life Time)。相同也能夠使用參數-XX:-UseCounterDecay來關閉熱度衰減。

 

方法調用計數器觸發即時編譯的整個流程例如以下圖所看到的:

這裏寫圖片描寫敘述

4.2.2 回邊計數器

什麼是回邊? 
在字節碼遇到控制流向後跳轉的指令稱爲回邊(Back Edge)。

回邊計數器是用來統計一個方法中循環體代碼運行的次數,回邊計數器的閾值能夠通過參數-XX:OnStackReplacePercentage來調整。

  • 虛虛擬機運行在Client模式下,回邊計數器閡值計算公式爲:
方法調用計數器閉值( CompileThreshold) xOSR比率(OnStackReplacePercentage) / 100

當中OnSlackReplacePercentage默認值爲933,假設都取默認值.那Client模式虛擬機的回邊計數器的閡值爲13995.

  • 虛擬機運行在Servo模式下,回邊計數器閡值的itm公式爲:
方法調用計數器閡值(CompileThmshold) x (OSR比率(OnStackReplacePercentage) - 解釋器監控比率(InterpreterProffePercentage) / 100

當中OnStackReplacePementage默認值爲140. InterpreterPmfilePercenmgc默認值爲33. 
假設都取默認值。BF Server模式虛擬機回邊計數器的闌值爲10700。

 

回邊計數器觸發即時編譯的流程例如以下圖所看到的:

這裏寫圖片描寫敘述

回邊計數器與方法調用計數器不同的是,回邊計數器沒有熱度衰減,因此這個計數器統計的就是循環運行的絕對次數。

五 編譯流程

在默認設置下,不管是方法調用產生的即時編譯請求,還是OSR編譯請求,虛擬機在代碼編譯器還未完畢之前,都仍然依照解釋方式繼續進行,而編譯動作則在後臺的編譯線程中繼續進行。也能夠使用-XX:-BackgroundCompilation來禁止後臺編譯,則此時一旦遇到JIT編譯,運行線程向虛擬機提交請求後會一直等待,直到編譯完畢後再開始運行編譯器輸出的本地代碼。

那麼在後臺編譯過程中,編譯器做了什麼事呢?

Server Compiler和Client Compiler的後臺編譯過程是不一樣的,我們來分別看一下。

5.1 Client Compiler編譯流程

  1. 第一階段:一個平臺獨立的前端將字節碼構造成一種高級中間碼錶示(High Level Infermediate Representaion),HIR使用靜態單分配的形式來表示代碼值,這能夠使得一些的構造過程之中和之後進行的優化動作更easy實現,在此之前編譯器會在字節碼上完畢一部分基礎優化,如方法內聯、常量傳播等。
  2. 第二階段:一個平臺相關的後端從HIR中產生低級中間代碼表示(Low Level Intermediate Representation),而在此之前會在HIR上完畢還有一些優化。如空值檢查消除、範圍檢查消除等。以便讓HIR達到更高效的代碼表示形式。
  3. 第三階段:在平臺相關的後端使用線性掃描算法(Linear Scan Register Allocation)在LIR上分配寄存器,並在LIR上做窺孔優化(Peephole)優化,然後產生機器碼。

整個步驟例如以下圖所看到的:

這裏寫圖片描寫敘述

5.1 Server Compiler編譯流程

Server Compiler是專門面向服務端的典型應用併爲服務器的性能配置特別調整過的編譯器,它會運行全部經典的優化動作。例如以下所看到的:

  • 無用代碼消除
  • 循環展開
  • 循環表達式外提
  • 消除公共子表達式
  • 常量傳播
  • 基本塊重排序
  • 範圍檢查消除
  • 空值檢查消除
  • 守護內聯
  • 分支頻率預測

 

轉自:https://www.cnblogs.com/mthoutai/p/7199091.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章