前提概要
解釋器
Java程序最初是通過解釋器(Interpreter)進行解釋執(zhí)行的重荠,當(dāng)虛擬機發(fā)現(xiàn)某個方法或代碼塊的運行特別頻繁的時候氯葬,就會把這些代碼認定為“熱點代碼”(hotspot code)。正因為如此捅暴,我們的hotspot的虛擬機就是因此而得名浅役。
解釋器優(yōu)點
(占用空間較少)解釋執(zhí)行占用更小的內(nèi)存空間。
(啟動和首次執(zhí)行速度較快)當(dāng)程序需要迅速啟動的時候伶唯,解釋器可以首先發(fā)揮作用,省去了編譯的時間惧盹,立即執(zhí)行乳幸。
(提高動態(tài)性和移植性)當(dāng)處于程序的動態(tài)效果下,如果預(yù)先編譯好所有相關(guān)的靜態(tài)本地代碼后钧椰,就無法實現(xiàn)動態(tài)化擴展粹断,以及提高移植到其他計算機平臺架構(gòu)下的能力
編譯器
為了提高熱點代碼的執(zhí)行效率,在運行時嫡霞,即時編譯器(Just In Time Compiler,下文稱 JIT編譯器 )會把這些代碼編譯成與本地平臺相關(guān)的機器碼瓶埋,并進行各種層次的優(yōu)化。
編譯器優(yōu)點
(提高運行速度)在程序運行時诊沪,隨著時間的推移养筒,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后端姚,可以獲得更高的執(zhí)行效率晕粪。
(逆轉(zhuǎn)優(yōu)化)同時,當(dāng)編譯器進行的激進優(yōu)化失敗的時候渐裸,還可以進行逆優(yōu)化來恢復(fù)到解釋執(zhí)行的狀態(tài)巫湘。
因此装悲,整個虛擬機執(zhí)行架構(gòu)中,解釋器與編譯器經(jīng)常配合工作尚氛,如下圖所示诀诊。
解釋器與編譯器并存的架構(gòu)(流程)
如果Java程序需要迅速啟動和執(zhí)行時,或者只是執(zhí)行一次阅嘶,解釋器可首先發(fā)揮作用属瓣,省去編譯時間,立即執(zhí)行程序運行后奈懒,隨著時間推移奠涌,JIT編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼后磷杏,可獲取更高執(zhí)行效率溜畅。
程序運行環(huán)境中內(nèi)存資源限制較大(如部分嵌入式系統(tǒng)中),可使用解釋執(zhí)行節(jié)約內(nèi)存极祸,反之可使用JIT編譯執(zhí)行提升效率
解釋器還可作為JIT編譯器激進優(yōu)化時的一個“逃生門”慈格,讓編譯器根據(jù)概率選擇一些大多數(shù)時候都能提升運行速度的優(yōu)化手段,當(dāng)激進優(yōu)化的假設(shè)不成立時可通過逆優(yōu)化(Deoptimization)退回到解釋狀態(tài)繼續(xù)執(zhí)行
故遥金,在整個虛擬機執(zhí)行架構(gòu)中解釋器與編譯器經(jīng)常配合工作
Xint設(shè)置:用戶可以使用參數(shù) -Xint 強制虛擬機運行于 “解釋模式”(Interpreted Mode)浴捆,這時候編譯器完全不介入工作。
-Xcomp設(shè)置:強制虛擬機運行于 “編譯模式”(Compiled Mode)稿械,這時候?qū)?yōu)先采用編譯方式執(zhí)行选泻,但是解釋器仍然要在編譯無法進行的情況下接入執(zhí)行過程。
-Xmixed設(shè)置:這種配合使用的方式稱為“混合模式”(Mixed Mode)美莫,
通過虛擬機 -version 命令可以查看當(dāng)前默認的運行模式页眯。
即時編譯器(JIT編譯器)
JIT編譯器不是虛擬機的必需部分,但JIT編譯器編譯性能的好壞厢呵、代碼優(yōu)化程度的高低是衡量一款商用虛擬機優(yōu)秀與否的最關(guān)鍵的指標(biāo)之一窝撵,也是虛擬機中最核心且最能體現(xiàn)虛擬機技術(shù)水平的部分。
被編譯對象和觸發(fā)條件
在運行過程中會被即時編譯的“熱點代碼”有兩類襟铭,即:
編譯的目標(biāo)對象
-
被多次調(diào)用的方法
- 編譯器會將整個方法作為編譯對象碌奉,這也是標(biāo)準(zhǔn)的JIT 編譯方式
-
被多次執(zhí)行的循環(huán)體
- 由循環(huán)體出發(fā)的,但是編譯器依然會以整個方法作為編譯對象寒砖,因為發(fā)生在方法執(zhí)行過程中赐劣,稱為棧上替換。
判斷熱點代碼
「判斷一段代碼是否是熱點代碼哩都,是不是需要出發(fā)即時編譯」隆豹,這樣的行為稱為熱點探測(Hot Spot Detection),探測算法有兩種茅逮,分別為璃赡。
基于采樣的熱點探測(Sample Based Hot Spot Detection)
虛擬機會周期的對各個線程棧頂進行檢查判哥,如果某些方法經(jīng)常出現(xiàn)在棧頂,這個方法就是“熱點方法”碉考。
優(yōu)點:實現(xiàn)簡單塌计、高效,很容易獲取方法調(diào)用關(guān)系侯谁。
缺點:很難確認方法的reduce(衰減)锌仅,容易受到線程阻塞或其他外因擾亂。
基于計數(shù)器的熱點探測(Counter Based Hot Spot Detection)
為每個方法(甚至是代碼塊)建立計數(shù)器墙贱,執(zhí)行次數(shù)超過閾值就認為是“熱點方法”热芹。
優(yōu)點:統(tǒng)計結(jié)果精確嚴(yán)謹。
缺點:實現(xiàn)麻煩惨撇,不能直接獲取方法的調(diào)用關(guān)系伊脓。
HotSpot使用的是第二種-基于技術(shù)其的熱點探測,并且有兩類計數(shù)器:
方法調(diào)用計數(shù)器(Invocation Counter )
回邊計數(shù)器(Back Edge Counter )
兩個即時編譯器
從上面的解釋器和編譯器的協(xié)同合作架構(gòu)圖中魁衙,應(yīng)該可以了解到报腔,JVM虛擬機實現(xiàn)了兩個不同的JIT編譯器,分別稱為 Client Compiler和 Server Compiler 剖淀,或者簡稱為 C1 編譯器和 C2 編譯器纯蛾。
熱點觸發(fā)的閾值
這兩個計數(shù)器都有一個確定的閾值,超過后便會觸發(fā)JIT編譯纵隔,具體細節(jié)和內(nèi)容下面會詳細講述翻诉。
上面提到了一下兩種熱點探測的計數(shù)器:
方法調(diào)用計數(shù)器(Invocation Counter )
-
首先是方法調(diào)用計數(shù)器:
Client模式下默認閾值是1500 次。
Server 模式下是 10000次捌刮。
這個閾值可以通過
-XX:CompileThreshold
來人為設(shè)定米丘。
如果不做任何設(shè)置,方法調(diào)用計數(shù)器統(tǒng)計的并不是方法被調(diào)用的絕對次數(shù)糊啡,而是一個相對的執(zhí)行頻率,即一段時間之內(nèi)的方法被調(diào)用的次數(shù)吁津。(可以理解為滑動窗口)棚蓄。
當(dāng)超過一定的時間限度,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時編譯器編譯碍脏,那么這個方法的調(diào)用計數(shù)器就會被減少一半梭依,這個過程稱為方法調(diào)用計數(shù)器熱度的衰減(Counter Decay),而這段時間就成為此方法的統(tǒng)計的半衰期( Counter Half Life Time)典尾。
進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的役拴,可以使用虛擬機參數(shù)
-XX:CounterHalfLifeTime
參數(shù)設(shè)置半衰周期的時間 (時間窗口秒),單位是秒钾埂。整個 JIT 編譯的交互過程如下圖河闰。
回邊計數(shù)器(Back Edge Counter )
作用是統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù)科平,在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊”( Back Edge )。
顯然姜性,建立回邊計數(shù)器統(tǒng)計的目的就是為了觸發(fā) OSR 編譯瞪慧。關(guān)于這個計數(shù)器的閾值, HotSpot 提供了
-XX:BackEdgeThreshold
供用戶設(shè)置部念。
但是當(dāng)前的虛擬機實際上使用了 -XX:OnStackReplacePercentage
來簡介調(diào)整閾值弃酌,計算公式如下:
Client模式, 公式為方法調(diào)用計數(shù)器閾值(CompileThreshold)X OSR 比率(OnStackReplacePercentage)/100 儡炼。其中OSR比率默認為933妓湘,那么,回邊計數(shù)器的閾值為13995乌询。
Server模式榜贴,公式為方法調(diào)用計數(shù)器閾值(Compile Threashold)X (OSR (OnStackReplacePercentage)- 解釋器監(jiān)控比率 (InterpreterProfilePercent))/100
其中onStackReplacePercentage 默認值為 140,InterpreterProfilePercentage 默認值為 33楣责,如果都取默認值竣灌,那么 Server 模式虛擬機回邊計數(shù)器閾值為 10700 。
編譯過程
默認情況下秆麸,無論是方法調(diào)用產(chǎn)生的即時編譯請求初嘹,還是OSR請求,虛擬機在代碼編譯器還未完成之前沮趣,都仍然將按照解釋方式繼續(xù)執(zhí)行屯烦,而編譯動作則在后臺的編譯線程中進行。
用戶可以通過參數(shù) -XX:-BackgroundCompilation
來禁止后臺編譯房铭,這樣驻龟,一旦達到 JIT 的編譯條件,執(zhí)行線程向虛擬機提交便已請求之后便會一直等待缸匪,直到編譯過程完成后再開始執(zhí)行編譯器輸出的本地代碼翁狐。
虛擬機運行模式
目前的HotSpot編譯器默認的是解釋器和其中一個即時編譯器配合的方式工作,具體是哪一個編譯器凌蔬,取決于虛擬機運行的模式露懒,HotSpot虛擬機會根據(jù)自身版本與計算機的硬件性能自動選擇運行模式,用戶也可以使用 -client 和 -server 參數(shù)強制指定虛擬機運行在 Client 模式或者 Server 模式砂心。
Client Compiler(了解即可) :
它是一個簡單快速的三段式編譯器懈词,主要關(guān)注點在于局部的優(yōu)化,放棄了許多耗時較長的全局優(yōu)化手段辩诞。
第一階段坎弯,一個平臺獨立的前端將字節(jié)碼構(gòu)造成一種高級中間代碼表示(High-Level Intermediate Representaion , HIR)。在此之前抠忘,編譯器會在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化撩炊,如 方法內(nèi)聯(lián),常量傳播等優(yōu)化褐桌。
第二階段衰抑,一個平臺相關(guān)的后端從 HIR 中產(chǎn)生低級中間代碼表示(Low-Level Intermediate Representation ,LIR)荧嵌,而在此之前會在 HIR 上完成另外一些優(yōu)化呛踊,如空值檢查消除,范圍檢查消除等啦撮,讓HIR 更為高效谭网。
第三階段,在平臺相關(guān)的后端使用線性掃描算法(Linear Scan Register Allocation)在 LIR 上分配寄存器赃春,做窺孔(Peephole)優(yōu)化愉择,然后產(chǎn)生機器碼。
Server Compiler(了解即可):
專門面向服務(wù)端典型應(yīng)用并為服務(wù)端性能配置特別調(diào)整過的編譯器
也是一個充分優(yōu)化過的高級編譯器织中,幾乎能達到GNU C++編譯器使用-02參數(shù)時的優(yōu)化強度會執(zhí)行所有經(jīng)典的優(yōu)化動作锥涕。
無用代碼消除(Dead Code Elimination)、
循環(huán)展開(LoopcUnrolling)狭吼、
循環(huán)表達式外提(Loop Expression Hoisting)层坠、
消除公共子表達式(Common Subexpression Elimination)、
常量傳播(Constant Propagation)刁笙、
基本塊重排序(Basic Block Reordering)等
還會實施一些與Java語言特性密切相關(guān)的優(yōu)化技術(shù)破花,如
范圍檢查消除(Range Check Elimination)、
空值檢查消除(Null Check Elimination)等
還可能根據(jù)解釋器或Client Compiler提供的性能監(jiān)控信息疲吸,進行一些不穩(wěn)定的激進優(yōu)化座每,如
守護內(nèi)聯(lián)(Guarded Inlining)、
分支頻率預(yù)測(Branch Frequency Prediction)等
Server Compiler的寄存器分配器是一個全局圖著色分配器摘悴,它可充分利用某些處理器架構(gòu)(如RISC)上的大寄存器集合
編譯速度遠超傳統(tǒng)靜態(tài)優(yōu)化編譯器峭梳,相對Client Compiler代碼質(zhì)量有所提高,可減少本地代碼執(zhí)行時間蹂喻,從而抵消額外的編譯時間開銷
如何從外部觀察即時編譯器的編譯過程和編譯結(jié)果葱椭?
-XX:+PrintCompilation 在即時編譯時,打印被編譯成本地代碼的方法名稱
-XX:+PrintInlining 在即時編譯時叉橱,輸出方法內(nèi)聯(lián)信息
-XX:+PrintAssembly 在即時編譯時,打印被編譯方法的匯編代碼者蠕,虛擬機需安裝反匯編適配器HSDIS插件窃祝,Product版虛擬機需加入?yún)?shù)-XX:+UnlockDiagnosticVMOptions打開虛擬機診斷模式
-XX:+PrintOptoAssembly 用于Server VM,輸出比較接近最終結(jié)果的中間代碼表示踱侣,不需HSDIS插件支持
-XX:+PrintLIR 用于Client VM粪小,輸出比較接近最終結(jié)果的中間代碼表示大磺,不需HSDIS插件支持
-XX:+PrintCFGToFile 用于Client Compiler,將編譯過程中各階段數(shù)據(jù)(如探膊,字節(jié)碼杠愧、HIR生成、LIR生成逞壁、寄存器分配過程流济、本地代碼生成等)輸出到文件中
-XX:PrintIdealGraphFile 用于Server Compiler,將編譯過程中各階段數(shù)據(jù)(如腌闯,字節(jié)碼绳瘟、HIR生成、LIR生成姿骏、寄存器分配過程糖声、本地代碼生成等)輸出到文件中
注,要輸出CFG或IdealGraph文件分瘦,需Debug或FastDebug版虛擬機支持蘸泻,Product版的虛擬機無法輸出這些文件