深入理解java虚拟机(十三) Java 即时编译器JIT机制以及编译优化
在部分的商用虛擬機中,Java 程序最初是通過解釋器( Interpreter )進行解釋執行的,當虛擬機發現某個方法或代碼塊的運行特別頻繁的時候,就會把這些代碼認定為 熱點代碼 。為了提高熱點代碼的執行效率,在運行時,即時編譯器(Just In Time Compiler )會把這些代碼編譯成與本地平臺相關的機器碼,并進行各種層次的優化。
1、HotSpot 內的即時編譯器
解釋器和編譯器各有各的優點:
解釋器優點:當程序需要迅速啟動的時候,解釋器可以首先發揮作用,省去了編譯的時間,立即執行。解釋執行占用更小的內存空間。同時,當編譯器進行的激進優化失敗的時候,還可以進行逆優化來恢復到解釋執行的狀態。
編譯器優點:在程序運行時,隨著時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲得更高的執行效率。
因此,整個虛擬機執行架構中,解釋器與編譯器經常配合工作,如下圖所示。
HotSpot中內置了兩個即時編譯器,分別稱為 Client Compiler和 Server Compiler ,或者簡稱為 C1 編譯器和 C2 編譯器。目前的 HotSpot 編譯器默認的是解釋器和其中一個即時編譯器配合的方式工作,具體是哪一個編譯器,取決于虛擬機運行的模式,HotSpot 虛擬機會根據自身版本與計算機的硬件性能自動選擇運行模式,用戶也可以使用 -client 和 -server 參數強制指定虛擬機運行在 Client 模式或者 Server 模式。這種配合使用的方式稱為“ 混合模式”(Mixed Mode),用戶可以使用參數 -Xint 強制虛擬機運行于 “解釋模式”(Interpreted Mode),這時候編譯器完全不介入工作。另外,使用 -Xcomp 強制虛擬機運行于 “編譯模式”(Compiled Mode),這時候將優先采用編譯方式執行,但是解釋器仍然要在編譯無法進行的情況下介入執行過程。通過虛擬機 -version 命令可以查看當前默認的運行模式。
2、被編譯對象和觸發條件
在運行過程中會被即時編譯的“熱點代碼”有兩類,即:
- 被多次調用的方法
- 被多次執行的循環體
對于第一種,編譯器會將整個方法作為編譯對象,這也是 標準的JIT 編譯方式。對于第二種是由循環體出發的,但是編譯器依然會以整個方法作為編譯對象,因為發生在方法執行過程中,稱為 棧上替換(OSR)。
判斷一段代碼是否是熱點代碼,是不是需要出發即時編譯,這樣的行為稱為 熱點探測(Hot Spot Detection),探測算法有兩種,分別為:
-
基于采樣的熱點探測(Sample Based Hot Spot Detection):虛擬機會周期的對各個線程棧頂進行檢查,如果某些方法經常出現在棧頂,這個方法就是“熱點方法”。好處是實現簡單、高效,很容易獲取方法調用關系。缺點是很難確認方法的reduce,容易受到線程阻塞或其他外因擾亂。
-
基于計數器的熱點探測(Counter Based Hot Spot Detection) :為每個方法(甚至是代碼塊)建立計數器,執行次數超過閾值就認為是“熱點方法”。優點是統計結果精確嚴謹。缺點是實現麻煩,不能直接獲取方法的調用關系。
HotSpot 使用的是第二種-基于計數器的熱點探測,并且有兩類計數器:方法調用計數器(Invocation Counter )和回邊計數器(Back Edge Counter )。
這兩個計數器都有一個確定的閾值,超過后便會觸發 JIT 編譯。
首先是方法調用計數器。 Client 模式下默認閾值是 1500 次,在 Server 模式下是 10000次,這個閾值可以通過 -XX:CompileThreadhold 來人為設定。如果不做任何設置,方法調用計數器統計的并不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間之內的方法被調用的次數。當超過一定的時間限度,如果方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那么這個方法的調用計數器就會被減少一半,這個過程稱為方法調用計數器熱度的衰減(Counter Decay),而這段時間就成為此方法的統計的半衰周期( Counter Half Life Time)。進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數 -XX:CounterHalfLifeTime 參數設置半衰周期的時間,單位是秒。整個 JIT 編譯的交互過程如下圖。
第二個回邊計數器, 作用是統計一個方法中循環體代碼執行的次數,在字節碼中遇到控制流向后跳轉的指令稱為“回邊”( Back Edge ,回邊可以有效的被認為是循環被執行完成的次數,不僅因為它是循環的結尾,也可能是因為它執行到了一個分支語句,例如 continue)。顯然,建立回邊計數器統計的目的就是為了觸發 OSR 編譯。關于這個計數器的閾值, HotSpot 提供了 -XX:BackEdgeThreshold 供用戶設置,但是當前的虛擬機實際上使用了 -XX:OnStackReplacePercentage 來間接調整閾值,計算公式如下:
在 Client 模式下, 公式為 方法調用計數器閾值(CompileThreshold)X OSR 比率(OnStackReplacePercentage)/ 100 。其中 OSR 比率默認為 933,那么,回邊計數器的閾值為 13995。
在 Server 模式下,公式為 方法調用計數器閾值(Compile Threashold)X (OSR (OnStackReplacePercentage)- 解釋器監控比率 (InterpreterProfilePercent))/100
其中 onStackReplacePercentage 默認值為 140,InterpreterProfilePercentage 默認值為 33,如果都取默認值,那么 Server 模式虛擬機回邊計數器閾值為 10700 。
執行過程,如下圖。
3、編譯過程
默認情況下,無論是方法調用產生的即時編譯請求,還是 OSR 請求,虛擬機在代碼編譯器還未完成之前,都仍然將按照解釋方式繼續執行,而編譯動作則在后臺的編譯線程中進行,用戶可以通過參數 -XX:-BackgroundCompilation 來禁止后臺編譯,這樣,一旦達到 JIT 的編譯條件,執行線程向虛擬機提交編譯請求之后便會一直等待,直到編譯過程完成后再開始執行編譯器輸出的本地代碼。
對于 Client 模式而言
它是一個簡單快速的三段式編譯器,主要關注點在于 局部的優化,放棄了許多耗時較長的全局優化手段。
第一階段,一個平臺獨立的前端將字節碼構造成一種高級中間代碼表示(High-Level Intermediate Representaion , HIR)。在此之前,編譯器會在字節碼上完成一部分基礎優化,如 方法內聯,常量傳播等優化。
第二階段,一個平臺相關的后端從 HIR 中產生低級中間代碼表示(Low-Level Intermediate Representation ,LIR),而在此之前會在 HIR 上完成另外一些優化,如空值檢查消除,范圍檢查消除等,讓HIR 更為高效。
第三階段,在平臺相關的后端使用線性掃描算法(Linear Scan Register Allocation)在 LIR 上分配寄存器,做窺孔(Peephole)優化,然后產生機器碼。
Client Compiler 的大致執行過程如下圖所示:
對于 Server Compiler 模式而言
它是專門面向服務端的典型應用,并為服務端的性能配置特別調整過的編譯器,也是一個充分優化過的高級編譯器,幾乎能達到 GNU C++ 編譯器使用-O2 參數時的優化強度,它會執行所有的經典的優化動作,如 無用代碼消除(Dead Code Elimination)、循環展開(Loop Unrolling)、循環表達式外提(Loop Expression Hoisting)、消除公共子表達式(Common Subexpression Elimination)、常量傳播(Constant Propagation)、基本塊沖排序(Basic Block Reordering)等,還會實施一些與 Java 語言特性密切相關的優化技術,如范圍檢查消除(Range Check Elimination)、空值檢查消除(Null Check Elimination ,不過并非所有的空值檢查消除都是依賴編譯器優化的,有一些是在代碼運行過程中自動優化 了)等。另外,還可能根據解釋器或Client Compiler 提供的性能監控信息,進行一些不穩定的激進優化,如 守護內聯(Guarded Inlining)、分支頻率預測(Branch Frequency Prediction)等。
Server Compiler 編譯器可以充分利用某些處理器架構,如(RISC)上的大寄存器集合。從即時編譯的角度來看, Server Compiler 無疑是比較緩慢的,但它的編譯速度仍遠遠超過傳統的靜態優化編譯器,而且它相對于 Client Compiler編譯輸出的代碼質量有所提高,可以減少本地代碼的執行時間,從而抵消了額外的編譯時間開銷,所以也有很多非服務端的應用選擇使用 Server 模式的虛擬機運行。
轉載:
原文作者: 張小琦
原文鏈接:https://blog.csdn.net/zq602316498/article/details/39152349
總結
以上是生活随笔為你收集整理的深入理解java虚拟机(十三) Java 即时编译器JIT机制以及编译优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Error:java: 无效的标记 -v
- 下一篇: Windows下关于Git的行结束符