深入理解JVM之JIT编译器(二)
上篇是分析了一下前段編譯器,主要過程完成從java代碼到字節(jié)碼的轉(zhuǎn)變,它的改進(jìn)頂多是提高程序的編碼速度和效率。本篇嘗試探索JIT編譯器,它能夠完成從字節(jié)碼到本地機(jī)器碼的轉(zhuǎn)變,從而真正的影響程序的運(yùn)行效率。
概念
部分商用虛擬機(jī),程序最初是通過解釋器(Interpreter)進(jìn)行解釋執(zhí)行,當(dāng)發(fā)現(xiàn)某個部分代碼頻繁執(zhí)行的時候,就會將這些代碼認(rèn)定為「熱點(diǎn)代碼」(即 Hot Spot Code)。為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時,虛擬機(jī)會把這些代碼編譯成與本地平臺相關(guān)的機(jī)器碼,并進(jìn)行各種優(yōu)化,完成這個任務(wù)的編譯器成為即時編譯器(Just In Time Compiler,簡稱JIT編譯器)。(下文所指均為JIT)關(guān)于即時編譯器需要知道的幾點(diǎn):
- 它并不是VM必需的部分,java虛擬機(jī)規(guī)范并沒有規(guī)定它必須存在,所以也沒限定如何去實現(xiàn)。
- 即時編譯器編譯性能的好壞、代碼優(yōu)化程度高低是衡量商用虛擬機(jī)優(yōu)秀與否最關(guān)鍵指標(biāo)之一。
Hotspot VM的JIT編譯器
解釋器和編譯器
不是所有Java虛擬機(jī)均采用二者并存的架構(gòu),但是一些主流商用虛擬機(jī)如Hotspot、J9都會同時包含二者。關(guān)于二者的優(yōu)勢如下:
在整個虛擬機(jī)執(zhí)行架構(gòu)中,解釋器和編譯器經(jīng)常配合工作,如下圖所示:
上圖中內(nèi)置了兩個JIT編譯器,分別稱Client Compiler和Server Compiler,簡稱為C1編譯器和C2編譯器。目前主流的Hotspot VM(jdk1.7及以前版本虛擬機(jī)),默認(rèn)采用與其中一個編譯器直接配合,程序用哪個,取決于虛擬機(jī)運(yùn)行模式,用戶可以使用-client和-server參數(shù)強(qiáng)制指定虛擬機(jī)運(yùn)行在client和server模式。
另外,通過下圖的三種命令可以強(qiáng)制虛擬機(jī)運(yùn)行的模式,分別為:Mixed Mode(混合模式,默認(rèn)情況下解釋器和編譯器搭配使用)、Interpreter Mode(解釋模式)、Compiled Mode(編譯模式)。
了解完解釋器和編譯器之后,我們想知道那些代碼會被編譯?在什么情況下會觸發(fā)編譯?基于這兩個問題,來了解編譯對象和觸發(fā)條件。
編譯對象
在運(yùn)行過程中會被即時編譯器編譯的「熱點(diǎn)代碼」有兩類,如下:
- 被多次調(diào)用的方法
- 被多次執(zhí)行的循環(huán)體
關(guān)于這兩種情況,有兩點(diǎn)解釋:對于第一種情況,是由方法調(diào)用觸發(fā)的編譯,理所當(dāng)然的會以整個方法體作為編譯對象,這種編譯是虛擬機(jī)中標(biāo)準(zhǔn)的JIT編譯。對于第二種情況,編譯動作由循環(huán)體觸發(fā),但是編譯器仍舊會以整個方法作為編譯對象,由于這種方法發(fā)生在方法執(zhí)行過程中,所以稱為棧上替換(On Stack Replacement,簡稱OSR編譯,因為方法棧幀還在棧上)。
觸發(fā)條件
正如上面所說的“多次”并不夠嚴(yán)謹(jǐn)和具體,那么如何才算是“多次”呢?以及怎么樣去統(tǒng)計一個方法和一段代碼被執(zhí)行多少次呢?回答了這兩個問題就是回答了觸發(fā)條件。判斷一段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時編譯,這樣的行為叫「熱點(diǎn)探測」。關(guān)于熱點(diǎn)探測有兩種方式,如下:
- 基于采樣的熱點(diǎn)探測
虛擬機(jī)會周期性檢查各個線程的棧頂,如果發(fā)現(xiàn)某些方法長期占據(jù)棧頂,那么會被認(rèn)為是「熱點(diǎn)代碼」。簡單、高效,但是不夠精確,因為某些方法會因為線程阻塞或其他原因擾亂熱點(diǎn)探測。 - 基于計數(shù)器的熱點(diǎn)探測(Hotspot VM采用)
虛擬機(jī)為每個方法或者代碼塊建立計數(shù)器,統(tǒng)計執(zhí)行次數(shù),超過一定閥值會被認(rèn)為是「熱點(diǎn)代碼」。
Hotspot VM使用第二種,并且為每個方法準(zhǔn)備了兩種計數(shù)器:方法調(diào)用計數(shù)器和回邊計數(shù)器(在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊 ”)。在確定虛擬運(yùn)行參數(shù)的情況下,這兩個計數(shù)器都有一個確定的閥值,超過這個閥值就會觸發(fā)JIT編譯。方法調(diào)用計數(shù)器觸發(fā)即時編譯的交互過程如下圖:
注意:方法調(diào)用計數(shù)器統(tǒng)計的并不是被調(diào)用的絕對次數(shù),而是一個相對的執(zhí)行頻率,即一段時間被被調(diào)用的次數(shù)。
編譯過程
在默認(rèn)設(shè)置下,無論是方法調(diào)用產(chǎn)生的即時編譯請求,還是OSR編譯請求,虛擬機(jī)在代碼編譯還未完成之前,都仍然按照解釋方式執(zhí)行,而編譯動作則在后臺編譯線程中進(jìn)行。至于在后臺如何執(zhí)行編譯過程,Client Compiler和Server Compiler的編譯過程是不同的。二者大致區(qū)別如下:
- Client Compiler:主要關(guān)注點(diǎn)在局部優(yōu)化,而放棄了耗時較長的全局優(yōu)化手段。
- Server Compiler:是專門面向服務(wù)器端的典型應(yīng)用并為服務(wù)端的性能配置特別調(diào)整過。
從即時編譯的角度看,Server Compiler是比較慢,但其編譯速度又遠(yuǎn)遠(yuǎn)超過「靜態(tài)優(yōu)化編譯器」,而且比Client Compiler輸出的代碼質(zhì)量高,可以減少本地代碼執(zhí)行時間,從而抵消額外的編譯時間。
編譯優(yōu)化技術(shù)
之所以有編譯方式執(zhí)行本地代碼比解釋方式更快這樣的共識,原因很簡單,是因為虛擬機(jī)設(shè)計團(tuán)隊幾乎把對代碼所有的優(yōu)化措施集中在了即時編譯器之中,因此一般來說,即時編譯器產(chǎn)生的本地代碼會比Javac產(chǎn)生的字節(jié)碼更優(yōu)秀。常用優(yōu)化技術(shù)如下:
公共子表達(dá)式消除
語言無關(guān),比如像:b乘c、c乘b這樣的表達(dá)式值都是一樣的可以直接替換。
數(shù)組范圍消除
語言相關(guān),主要思路就是盡可能把運(yùn)行期檢查提任務(wù)前到編譯器進(jìn)行,以至于在循環(huán)遍歷的時候不需要每次都要判斷變量大小是否超過數(shù)組范圍,帶來隱式開銷,只要在編譯期根據(jù)數(shù)據(jù)流獲得數(shù)組的length,并且判斷下標(biāo)沒有越界,執(zhí)行的時候就無需判斷了。
方法內(nèi)聯(lián)
為了消除方法調(diào)用的成本,同時為其他優(yōu)化手段建立好的基礎(chǔ)。因為很多方法分開看是有意義的,如果不做方法內(nèi)聯(lián),即使進(jìn)行了無用代碼消除,也無法發(fā)現(xiàn)任何“Dead Code”。
逃逸分析
并不是直接的優(yōu)化手段,而是為其他優(yōu)化手段提供分析技術(shù)。逃逸分析的基本行為就是分析對象的作用域,比如一個對象在一個方法中被定義,可能被外部方法所引用,例如作為方法參數(shù)傳遞到其他方法中,這被稱為方法逃逸。甚至可能被其他線程訪問,例如賦值給類變量或其他線程訪問的實例變量,就是線程逃逸。
因此,如果能證明一個不會發(fā)生方法或者線程逃逸,則可以為這個變量進(jìn)行一些高效優(yōu)化。優(yōu)化措施如下:
- 棧上分配
將確定不會逃逸的對象在在棧幀上進(jìn)行創(chuàng)建分配內(nèi)存,這樣方法結(jié)束時,對象就會隨著棧幀出棧而銷毀,減少堆內(nèi)存垃圾回收的壓力。 - 同步消除
如果確定一個變量不會線程逃逸,也就說明該變量不會發(fā)生線程競爭,從而消除掉該變量的同步措施。 - 標(biāo)量替換
像java中的原始數(shù)據(jù)類型如int、long均稱為標(biāo)量(表示無法分解為更小的數(shù)據(jù)來表示了),如果一個數(shù)據(jù)可以繼續(xù)分解,則為聚合量,而java中的對象則為典型的聚合量。如果一個對象不會被外部訪問,而這個對象可以被拆散分解,那么就不去創(chuàng)建這個對象,而是直接創(chuàng)建被使用到的成員變量。而這些成員變量除了被分配在棧上(棧上的數(shù)據(jù)很容易會被虛擬機(jī)分配到物理高速寄存器)進(jìn)行讀和寫,還為后續(xù)優(yōu)化創(chuàng)造基礎(chǔ)條件。
本篇主要了解解釋器和編譯器的特點(diǎn)和各自優(yōu)勢、然后總結(jié)了編譯對象和編譯條件,最后介紹了編譯過程以及用到的一些主要的編譯優(yōu)化技術(shù),算是對即時編譯器有個基本的理解了。
from:http://zouzls.github.io/2016/09/07/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3JVM%E4%B9%8BJIT%E7%BC%96%E8%AF%91%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/?
總結(jié)
以上是生活随笔為你收集整理的深入理解JVM之JIT编译器(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解JVM之前端编译器(一)
- 下一篇: javac 编译与 JIT 编译