轉(zhuǎn)載自(侵刪):https://www.cnblogs.com/insistence/p/5901457.html
1. 什么是Just In Time編譯器?
Hot Spot 編譯
當(dāng) JVM 執(zhí)行代碼時(shí),它并不立即開始編譯代碼痰滋。這主要有兩個(gè)原因:
首先,如果這段代碼本身在將來只會(huì)被執(zhí)行一次膛堤,那么從本質(zhì)上看,編譯就是在浪費(fèi)精力摊阀。因?yàn)閷⒋a翻譯成 java 字節(jié)碼相對(duì)于編譯這段代碼并執(zhí)行代碼來說童芹,要快很多。
當(dāng) 然便脊,如果一段代碼頻繁的調(diào)用方法,或是一個(gè)循環(huán)光戈,也就是這段代碼被多次執(zhí)行哪痰,那么編譯就非常值得了遂赠。因此,編譯器具有的這種權(quán)衡能力會(huì)首先執(zhí)行解釋后的代 碼晌杰,然后再去分辨哪些方法會(huì)被頻繁調(diào)用來保證其本身的編譯跷睦。其實(shí)說簡(jiǎn)單點(diǎn),就是 JIT 在起作用肋演,我們知道抑诸,對(duì)于 Java 代碼,剛開始都是被編譯器編譯成字節(jié)碼文件惋啃,然后字節(jié)碼文件會(huì)被交由 JVM 解釋執(zhí)行哼鬓,所以可以說 Java 本身是一種半編譯半解釋執(zhí)行的語言。Hot Spot VM 采用了 JIT compile 技術(shù)边灭,將運(yùn)行頻率很高的字節(jié)碼直接編譯為機(jī)器指令執(zhí)行以提高性能,所以當(dāng)字節(jié)碼被 JIT 編譯為機(jī)器碼的時(shí)候健盒,要說它是編譯執(zhí)行的也可以绒瘦。也就是說,運(yùn)行時(shí)扣癣,部分代碼可能由 JIT 翻譯為目標(biāo)機(jī)器指令(以 method 為翻譯單位惰帽,還會(huì)保存起來,第二次執(zhí)行就不用翻譯了)直接執(zhí)行父虑。
第二個(gè)原因是最優(yōu)化该酗,當(dāng) JVM 執(zhí)行某一方法或遍歷循環(huán)的次數(shù)越多,就會(huì)更加了解代碼結(jié)構(gòu)士嚎,那么 JVM 在編譯代碼的時(shí)候就做出相應(yīng)的優(yōu)化呜魄。
我 們將在后面講解這些優(yōu)化策略,這里莱衩,先舉一個(gè)簡(jiǎn)單的例子:我們知道 equals() 這個(gè)方法存在于每一個(gè) Java Object 中(因?yàn)槭菑?Object class 繼承而來)而且經(jīng)常被覆寫爵嗅。當(dāng)解釋器遇到 b = obj1.equals(obj2) 這樣一句代碼,它則會(huì)查詢 obj1 的類型從而得知到底運(yùn)行哪一個(gè) equals() 方法笨蚁。而這個(gè)動(dòng)態(tài)查詢的過程從某種程度上說是很耗時(shí)的睹晒。
在主流商用JVM(HotSpot、J9)中括细,Java程序一開始是通過解釋器(Interpreter)進(jìn)行解釋執(zhí)行的伪很。當(dāng)JVM發(fā)現(xiàn)某個(gè)方法或代碼塊運(yùn)行特別頻繁時(shí),就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼(Hot Spot Code)”奋单,然后JVM會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼锉试,并進(jìn)行各種層次的優(yōu)化,完成這個(gè)任務(wù)的編譯器稱為:即時(shí)編譯器(Just In Time Compiler辱匿,JIT)
JIT編譯器是“動(dòng)態(tài)編譯器”的一種键痛,相對(duì)的“靜態(tài)編譯器”則是指的比如:C/C++的編譯器
JIT并不是JVM的必須部分炫彩,JVM規(guī)范并沒有規(guī)定JIT必須存在,更沒有限定和指導(dǎo)JIT絮短。但是江兢,JIT性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款JVM是否優(yōu)秀的最關(guān)鍵指標(biāo)之一丁频,也是虛擬機(jī)中最核心且最能體現(xiàn)虛擬機(jī)技術(shù)水平的部分杉允。
2. 編譯器與解釋器
首先,不是所有JVM都采用編譯器和解釋器并存的架構(gòu)席里,但主流商用虛擬機(jī)叔磷,都同時(shí)包含這兩部分。
2.1 配合過程
當(dāng)程序需要迅速啟動(dòng)然后執(zhí)行的時(shí)候奖磁,解釋器可以首先發(fā)揮作用改基,編譯器不運(yùn)行從而省去編譯時(shí)間,立即執(zhí)行程序
在程序運(yùn)行后咖为,隨著時(shí)間的推移秕狰,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后躁染,可以獲得更高的執(zhí)行效率
當(dāng)程序運(yùn)行環(huán)境中內(nèi)存資源限制較大(如部分嵌入式系統(tǒng)中)鸣哀,可以使用解釋執(zhí)行來節(jié)約內(nèi)存;反之吞彤,則可以使用編譯執(zhí)行來提升效率我衬。
同時(shí),解釋器還可以作為編譯器(C2才會(huì)激進(jìn)優(yōu)化)激進(jìn)優(yōu)化時(shí)的一個(gè)“逃生門”饰恕,讓編譯器根據(jù)概率選擇一些大多數(shù)時(shí)候都能提升運(yùn)行速度的優(yōu)化手段挠羔,當(dāng)激進(jìn)優(yōu)化假設(shè)不成立。如:加載了新類后懂盐,類型繼承結(jié)構(gòu)出現(xiàn)變化褥赊,出現(xiàn)“罕見陷阱(Uncommon Trap)”時(shí),可以通過逆優(yōu)化(Deoptimization)退回到解釋狀態(tài)繼續(xù)執(zhí)行
(部分沒有解釋器的虛擬機(jī)莉恼,也會(huì)采用不進(jìn)行激進(jìn)優(yōu)化的C1編譯器擔(dān)任“逃生門”的角色)
2.2 解釋器 - Interpreter
Interpreter解釋執(zhí)行class文件拌喉,好像JavaScript執(zhí)行引擎一樣
特殊的例子:
- 最早的Sun Classic VM只有Interpreter
- BEA JRockit VM則只有Compiler,但它主要面向服務(wù)端應(yīng)用俐银,部署在其上的應(yīng)用不重點(diǎn)關(guān)注啟動(dòng)時(shí)間
2.3 編譯器 - Compiler
只說HotSpot JVM
1. C1和C2:
HotSpot虛擬機(jī)內(nèi)置了兩個(gè)即時(shí)編譯器尿背,分別稱為Client Compiler和Server Compiler,習(xí)慣上將前者稱為C1捶惜,后者稱為C2
2. 使用C1還是C2田藐?
HotSpot默認(rèn)采用解釋器和其中一個(gè)編譯器直接配合的方式工作,使用那個(gè)編譯器取決于虛擬機(jī)運(yùn)行的模式,HotSpot會(huì)根據(jù)自身版本和宿主機(jī)器硬件性能自動(dòng)選擇模式汽久,用戶也可以使用“-client”或”-server”參數(shù)去指定
混合模式(Mixed Mode)
默認(rèn)的模式鹤竭,如上面描述的這種方式就是mixed mode解釋模式(Interpreted Mode)
可以使用參數(shù)“-Xint”,在此模式下全部代碼解釋執(zhí)行-
編譯模式(Compiled Mode)
參數(shù)“-Xcomp”景醇,此模式優(yōu)先采用編譯臀稚,但是無法編譯時(shí)也會(huì)解釋(在最新的HotSpot中此參數(shù)被取消)可以看到,我的JVM現(xiàn)在是mixed mode
$ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
重要:↓
在JDK1.7(1.7僅包括Server模式)之后三痰,HotSpot就不是默認(rèn)“采用解釋器和其中一個(gè)編譯器”配合的方式了吧寺,而是采用了分層編譯,分層編譯時(shí)C1和C2有可能同時(shí)工作
3. 分層編譯
3.1 為什么要分層編譯散劫?
由于編譯器compile本地代碼需要占用程序時(shí)間稚机,要編譯出優(yōu)化程度更高的代碼所花費(fèi)的時(shí)間可能更長(zhǎng),且此時(shí)解釋器還要替編譯器收集性能監(jiān)控信息获搏,這對(duì)解釋執(zhí)行的速度也有影響
所以赖条,為了在程序啟動(dòng)響應(yīng)時(shí)間與運(yùn)行效率之間達(dá)到最佳平衡,HotSpot在JDK1.6中出現(xiàn)了分層編譯(Tiered Compilation)的概念并在JDK1.7的Server模式JVM中作為默認(rèn)策略被開啟
3.2 編譯層 tier(或者叫級(jí)別)
分層編譯根據(jù)編譯器編譯常熙、優(yōu)化的規(guī)模與耗時(shí)谋币,劃分了不同的編譯層次(不只以下3種),包括:
第0層症概,程序解釋執(zhí)行(沒有編譯),解釋器不開啟性能監(jiān)控功能早芭,可觸發(fā)第1層編譯彼城。
第1層,也稱C1編譯退个,將字節(jié)碼編譯為本地代碼募壕,進(jìn)行簡(jiǎn)單、可靠的優(yōu)化语盈,如有必要將加入性能監(jiān)控的邏輯
第2層(或2層以上)舱馅,也稱為C2編譯,也是將字節(jié)碼編譯為本地代碼刀荒,但是會(huì)啟用一些編譯耗時(shí)較長(zhǎng)的優(yōu)化代嗤,甚至?xí)?/strong>根據(jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化
實(shí)施分層編譯后,C1和C2將會(huì)同時(shí)工作缠借,許多代碼會(huì)被多次編譯干毅,用C1獲取更高的編譯速度,用C2來獲取更好的編譯質(zhì)量泼返,且在解釋執(zhí)行的時(shí)候解釋器也無須再承擔(dān)收集性能監(jiān)控信息的任務(wù)
4. 編譯對(duì)象與觸發(fā)條件
1. 誰被編譯了硝逢?
編譯對(duì)象就是之前說的“熱點(diǎn)代碼”,它有兩類:
-
被多次調(diào)用的方法
- 一個(gè)方法被多次調(diào)用,理應(yīng)稱為熱點(diǎn)代碼渠鸽,這種編譯也是虛擬機(jī)中標(biāo)準(zhǔn)的JIT編譯方式
-
被多次執(zhí)行的循環(huán)體
- 編譯動(dòng)作由循環(huán)體出發(fā)叫乌,但編譯對(duì)象依然會(huì)以整個(gè)方法為對(duì)象;
- 這種編譯方式由于編譯發(fā)生在方法執(zhí)行過程中徽缚,因此形象的稱為:棧上替換(On Stack Replacement- OSR編譯憨奸,即方法棧幀還在棧上,方法就被替換了)
2. 觸發(fā)條件
1. 綜述
上面的方法和循環(huán)體都說“多次”猎拨,那么多少算多膀藐?換個(gè)說法就是編譯的觸發(fā)條件。
判斷一段代碼是不是熱點(diǎn)代碼红省,是不是需要觸發(fā)JIT編譯额各,這樣的行為稱為:熱點(diǎn)探測(cè)(Hot Spot Detection),有幾種主流的探測(cè)方式:
基于計(jì)數(shù)器的熱點(diǎn)探測(cè)(Counter Based Hot Spot Detection)
虛擬機(jī)會(huì)為每個(gè)方法(或每個(gè)代碼塊)建立計(jì)數(shù)器吧恃,統(tǒng)計(jì)執(zhí)行次數(shù)虾啦,如果超過閥值那么就是熱點(diǎn)代碼。缺點(diǎn)是維護(hù)計(jì)數(shù)器開銷痕寓。基于采樣的熱點(diǎn)探測(cè)(Sample Based Hot Spot Detection)
虛擬機(jī)會(huì)周期性檢查各個(gè)線程的棧頂傲醉,如果某個(gè)方法經(jīng)常出現(xiàn)在棧頂,那么就是熱點(diǎn)代碼呻率。缺點(diǎn)是不精確硬毕。基于蹤跡的熱點(diǎn)探測(cè)(Trace Based Hot Spot Detection)
Dalvik中的JIT編譯器使用這種方式
2. HotSpot
HotSpot使用的是第1種,因此它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter)和回邊計(jì)數(shù)器(Back Edge Counter)
-
方法計(jì)數(shù)器
默認(rèn)閥值礼仗,在Client模式下是1500次吐咳,Server是10000次,可以通過參數(shù)“-XX:CompileThreshold”來設(shè)定
當(dāng)一個(gè)方法被調(diào)用時(shí)會(huì)首先檢查是否存在被JIT編譯過得版本元践,如果存在則使用此本地代碼來執(zhí)行韭脊;如果不存在,則將方法計(jì)數(shù)器+1单旁,然后判斷“方法計(jì)數(shù)器和回邊計(jì)數(shù)器之和”是否超過閥值沪羔,如果是則會(huì)向編譯器提交一個(gè)方法編譯請(qǐng)求
默認(rèn)情況下,執(zhí)行引擎并不會(huì)同步等待上面的編譯完成象浑,而是會(huì)繼續(xù)解釋執(zhí)行蔫饰。當(dāng)編譯完成后,此方法的調(diào)用入口地址會(huì)被系統(tǒng)自動(dòng)改寫為新的本地代碼地址
還有一點(diǎn)融柬,熱度是會(huì)衰減的死嗦,也就是說不是僅僅+,也會(huì)-粒氧,熱度衰減動(dòng)作是在虛擬機(jī)的GC執(zhí)行時(shí)順便進(jìn)行的
-
回邊計(jì)數(shù)器
回邊越除,顧名思義,只有執(zhí)行到大括號(hào)”}”時(shí)才算+1
默認(rèn)閥值,Client下13995摘盆,Server下10700
它的調(diào)用邏輯和方法計(jì)數(shù)器差不多翼雀,只不過遇到回邊指令時(shí)+1、超過閥值時(shí)會(huì)提交OSR編譯請(qǐng)求以及這里沒有熱度衰減
5. 編譯過程
編譯過程是在后臺(tái)線程(daemon)中完成的孩擂,可以通過參數(shù)“-XX:-BackgroundCompilation”來禁止后臺(tái)編譯狼渊,但此時(shí)執(zhí)行線程就會(huì)同步等待編譯完成才會(huì)執(zhí)行程序
- Client Compiler
C1編譯器是一個(gè)簡(jiǎn)單快速的三段式編譯器,主要關(guān)注“局部性能優(yōu)化”类垦,放棄許多耗時(shí)較長(zhǎng)的全局優(yōu)化手段
過程:class -> 1. 高級(jí)中間代碼 -> 2. 低級(jí)中間代碼 -> 3. 機(jī)器代碼 - Server Compiler
C2是專門面向服務(wù)器應(yīng)用的編譯器狈邑,是一個(gè)充分優(yōu)化過的高級(jí)編譯器,幾乎能達(dá)到GNU C++編譯器使用-O2參數(shù)時(shí)的優(yōu)化強(qiáng)度蚤认。
使用參數(shù)“-XX:+PrintCompilation”會(huì)讓虛擬機(jī)在JIT時(shí)把方法名稱打印出來米苹,如圖:
6. Java和C/C++的編譯器對(duì)比
這里不是比Java和C/C++誰快這種大坑問題,只是比較編譯器(我認(rèn)為開發(fā)效率上Java快砰琢,執(zhí)行效率上C/C++快)
這種對(duì)比代表了經(jīng)典的即時(shí)編譯器與靜態(tài)編譯期的對(duì)比蘸嘶,其實(shí)總體來說Java編譯器有優(yōu)有劣。主要就是動(dòng)態(tài)編譯時(shí)間壓力大能做的優(yōu)化少陪汽,還要做一些動(dòng)態(tài)校驗(yàn)训唱。而靜態(tài)編譯器無法實(shí)現(xiàn)一些開發(fā)上很有用的動(dòng)態(tài)特性