Java JIT 知識(shí)

java編譯器筑舅,java解釋器

1.java程序是一種可跨平臺(tái)執(zhí)行的語言媒吗,之所以可以跨平臺(tái),是因?yàn)閖vm的存在坝疼,JVM屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息搜贤,使java文件只需要生成為jvm可識(shí)別的字節(jié)碼(*.class)文件,jvm會(huì)將字節(jié)碼文件交給解釋器钝凶,翻譯成機(jī)器碼入客,由解釋器執(zhí)行,JVM解釋執(zhí)行字節(jié)碼文件就是JVM操作Java解釋器進(jìn)行解釋執(zhí)行字節(jié)碼文件的過程腿椎。這樣保證了在任何平臺(tái)上桌硫,都可以成功的執(zhí)行java程序,jvm的存在就是java可跨平臺(tái)的核心啃炸。

2.解釋器是把高級(jí)語言一行一行直接翻譯再運(yùn)行铆隘,它不會(huì)一次性把整個(gè)文件都翻譯過來,而是翻譯一句南用,執(zhí)行一句膀钠,再翻譯,再執(zhí)行裹虫,所以解釋器的程序運(yùn)行起來會(huì)比較慢肿嘲,每次都要解釋之后再執(zhí)行。

3.動(dòng)態(tài)編譯(dynamic compilation)指的是“在運(yùn)行時(shí)進(jìn)行編譯”筑公;與之相對(duì)的是事前編譯(ahead-of-time compilation雳窟,簡(jiǎn)稱AOT),也叫靜態(tài)編譯(static compilation)匣屡。

什么是JIT

1.JIT編譯(just-in-time?compilation)狹義來說是當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯封救,因而叫“即時(shí)編譯”。JIT編譯是動(dòng)態(tài)編譯的一種特例捣作。JIT編譯一詞后來被泛華誉结,時(shí)常與動(dòng)態(tài)編譯等價(jià);但要注意廣義與狹義的JIT編譯所指的區(qū)別券躁。JIT(即時(shí)編譯)是用來提高java程序運(yùn)行效率的惩坑,原本字節(jié)碼由解釋器需要經(jīng)過解釋再運(yùn)行掉盅,現(xiàn)在有了JIT技術(shù),將字節(jié)碼編譯成平臺(tái)相關(guān)的原生機(jī)器碼以舒,并進(jìn)行各個(gè)層次的優(yōu)化怔接,這些機(jī)器碼會(huì)被緩存起來,以備下次使用稀轨,如果JIT對(duì)每條字節(jié)碼都進(jìn)行編譯扼脐,緩存(緩存的指令是有限的),會(huì)增加開銷奋刽,因此JIT只對(duì)熱點(diǎn)代碼進(jìn)行即時(shí)編譯瓦侮,如循環(huán),高頻度使用的方法佣谐,會(huì)將整個(gè)方法編譯成本地機(jī)器碼肚吏,然后直接運(yùn)行機(jī)器碼。

java虛擬機(jī)并沒有規(guī)定一定要有JIT狭魂,但是罚攀,即時(shí)編譯器編譯性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機(jī)優(yōu)秀與否的最關(guān)鍵的指標(biāo)之一雌澄,它也是虛擬機(jī)中最核心且最能體現(xiàn)虛擬機(jī)技術(shù)水平的部分斋泄。

由于Java虛擬機(jī)規(guī)范并沒有具體的約束規(guī)則去限制即使編譯器應(yīng)該如何實(shí)現(xiàn),所以這部分功能完全是與虛擬機(jī)具體實(shí)現(xiàn)相關(guān)的內(nèi)容镐牺,如無特殊說明炫掐,我們提到的編譯器、即時(shí)編譯器都是指Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器睬涧,虛擬機(jī)也是特指HotSpot虛擬機(jī)募胃。



為什么HotSpot要使用編譯器和解釋器并存的模式

1.盡管并不是所有的Java虛擬機(jī)都采用解釋器與編譯器并存的架構(gòu),但許多主流的商用虛擬機(jī)(如HotSpot)畦浓,都同時(shí)包含解釋器和編譯器痹束。解釋器與編譯器兩者各有優(yōu)勢(shì):當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用讶请,省去編譯的時(shí)間祷嘶,立即執(zhí)行。在程序運(yùn)行后秽梅,隨著時(shí)間的推移抹蚀,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后企垦,可以獲取更高的執(zhí)行效率。當(dāng)程序運(yùn)行環(huán)境中內(nèi)存資源限制較大(如部分嵌入式系統(tǒng)中)晒来,可以使用解釋器執(zhí)行節(jié)約內(nèi)存钞诡,反之可以使用編譯執(zhí)行來提升效率。此外,如果編譯后出現(xiàn)“罕見陷阱”荧降,可以通過逆優(yōu)化退回到解釋執(zhí)行接箫。

2.編譯器的時(shí)間開銷和空間開銷:

解釋器的執(zhí)行,抽象的看是這樣的:
字節(jié)碼 -> [ 解釋器 解釋執(zhí)行機(jī)器碼 ] -> 執(zhí)行結(jié)果
而要JIT編譯然后再執(zhí)行的話朵诫,抽象的看則是:
字節(jié)碼 -> [ 編譯器 編譯 ] -> 與機(jī)器相關(guān)的機(jī)器碼-> [ 執(zhí)行 ] -> 執(zhí)行結(jié)果

說JIT比解釋快辛友,其實(shí)說的是“執(zhí)行編譯后的代碼”比“解釋器解釋執(zhí)行”要快,并不是說“編譯”這個(gè)動(dòng)作比“解釋”這個(gè)動(dòng)作快剪返。

JIT編譯再怎么快废累,至少也比解釋執(zhí)行一次略慢一些,而要得到最后的執(zhí)行結(jié)果還得再經(jīng)過一個(gè)“執(zhí)行編譯后的代碼”的過程脱盲。所以邑滨,對(duì)“只執(zhí)行一次”的代碼而言,解釋執(zhí)行其實(shí)總是比JIT編譯執(zhí)行要快钱反。怎么算是“只執(zhí)行一次的代碼”呢掖看?粗略說,下面兩個(gè)條件同時(shí)滿足時(shí)就是嚴(yán)格的“只執(zhí)行一次”

1面哥、只被調(diào)用一次哎壳,例如類的構(gòu)造器(class initializer,())
2尚卫、沒有循環(huán)

對(duì)只執(zhí)行一次的代碼做JIT編譯再執(zhí)行耳峦,可以說是得不償失。對(duì)只執(zhí)行少量次數(shù)的代碼焕毫,JIT編譯帶來的執(zhí)行速度的提升也未必能抵消掉最初編譯帶來的開銷蹲坷。只有對(duì)頻繁執(zhí)行的代碼,JIT編譯才能保證有正面的收益邑飒。

對(duì)一般的Java方法而言循签,編譯后代碼的大小相對(duì)于字節(jié)碼的大小,膨脹比達(dá)到10x是很正常的疙咸。同上面說的時(shí)間開銷一樣县匠,這里的空間開銷也是,只有對(duì)執(zhí)行頻繁的代碼才值得編譯撒轮,如果把所有代碼都編譯則會(huì)顯著增加代碼所占空間乞旦,導(dǎo)致“代碼爆炸”。這也就解釋了為什么有些JVM會(huì)選擇不總是做JIT編譯题山,而是選擇用解釋器+JIT編譯器的混合執(zhí)行引擎兰粉。

哪些程序代碼會(huì)被編譯為本地代碼?如何編譯為本地代碼顶瞳?

程序中的代碼只有是熱點(diǎn)代碼時(shí)玖姑,才會(huì)編譯為本地代碼愕秫,那么什么是熱點(diǎn)代碼呢?運(yùn)行過程中會(huì)被即時(shí)編譯器編譯的“熱點(diǎn)代碼”有兩類:

1焰络、被多次調(diào)用的方法戴甩。
2、被多次執(zhí)行的循環(huán)體闪彼。

兩種情況甜孤,編譯器都是以整個(gè)方法作為編譯對(duì)象乒疏。 這種編譯方法因?yàn)榫幾g發(fā)生在方法執(zhí)行過程之中贝奇,因此形象的稱之為棧上替換(On Stack Replacement助赞,OSR)净刮,即方法棧幀還在棧上笆环,方法就被替換了凛驮。

如何判斷方法或一段代碼或是不是熱點(diǎn)代碼呢木人?

在HotSpot虛擬機(jī)中使用的基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方法缩挑,因此它為每個(gè)方法準(zhǔn)備了兩個(gè)計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器流昏。在確定虛擬機(jī)運(yùn)行參數(shù)的前提下扎即,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過閾值溢出了况凉,就會(huì)觸發(fā)JIT編譯谚鄙。

方法調(diào)用計(jì)數(shù)器:顧名思義,這個(gè)計(jì)數(shù)器用于統(tǒng)計(jì)方法被調(diào)用的次數(shù)刁绒。

當(dāng)一個(gè)方法被調(diào)用時(shí)闷营,會(huì)先檢查該方法是否存在被JIT編譯過的版本,如果存在知市,則優(yōu)先使用編譯后的本地代碼來執(zhí)行傻盟。如果不存在已被編譯過的版本,則將此方法的調(diào)用計(jì)數(shù)器值加1嫂丙,然后判斷方法調(diào)用計(jì)數(shù)器與回邊計(jì)數(shù)器值之和是否超過方法調(diào)用計(jì)數(shù)器的閾值娘赴。如果超過閾值,那么將會(huì)向即時(shí)編譯器提交一個(gè)該方法的代碼編譯請(qǐng)求跟啤。

如果不做任何設(shè)置诽表,執(zhí)行引擎并不會(huì)同步等待編譯請(qǐng)求完成,而是繼續(xù)進(jìn)行解釋器按照解釋方式執(zhí)行字節(jié)碼隅肥,直到提交的請(qǐng)求被編譯器編譯完成竿奏。當(dāng)編譯工作完成之后,這個(gè)方法的調(diào)用入口地址就會(huì)系統(tǒng)自動(dòng)改寫成新的腥放,下一次調(diào)用該方法時(shí)就會(huì)使用已編譯的版本泛啸。


回變計(jì)數(shù)器:它的作用就是統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊”捉片。


六.如何編譯為本地代碼

Server Compiler和Client Compiler兩個(gè)編譯器的編譯過程是不一樣的平痰。

對(duì)Client Compiler來說汞舱,它是一個(gè)簡(jiǎn)單快速的編譯器伍纫,主要關(guān)注點(diǎn)在于局部?jī)?yōu)化宗雇,而放棄許多耗時(shí)較長(zhǎng)的全局優(yōu)化手段。

而Server Compiler則是專門面向服務(wù)器端的莹规,并為服務(wù)端的性能配置特別調(diào)整過的編譯器赔蒲,是一個(gè)充分優(yōu)化過的高級(jí)編譯器。

1良漱、Client Compiler
簡(jiǎn)稱C1編譯器舞虱;
(A)應(yīng)用特點(diǎn)
較為輕量,只做少量性能開銷比較高的優(yōu)化母市,它占用內(nèi)存較少矾兜,適合于桌面交互式應(yīng)用。

(B)優(yōu)化技術(shù)
它是一個(gè)簡(jiǎn)單快速的三段式編譯器患久,主要關(guān)注點(diǎn)在于局部性的優(yōu)化椅寺,而放棄了許多耗時(shí)較長(zhǎng)的全局優(yōu)化;在寄存器分配策略上蒋失,JDK6以后采用的為線性掃描寄存器分配算法返帕,其他方面的優(yōu)化,主要有方法內(nèi)聯(lián)篙挽、去虛擬化荆萤、冗余消除等;

(C)設(shè)置參數(shù)
可以使用"-client"參數(shù)強(qiáng)制選擇運(yùn)行在Client模式(Client VM)

2铣卡、Server Compiler
簡(jiǎn)稱C2編譯器链韭,也叫Opto編譯器;
(A)應(yīng)用特點(diǎn)
較為重量煮落,采用了大量傳統(tǒng)編譯優(yōu)化的技巧來進(jìn)行優(yōu)化敞峭,占用內(nèi)存相對(duì)多一些,適合服務(wù)器端的應(yīng)用州邢。

(B)優(yōu)化技術(shù)
它會(huì)執(zhí)行所有經(jīng)典的優(yōu)化動(dòng)作儡陨,如無用代碼消除、循環(huán)展開量淌、循環(huán)表達(dá)式外提骗村、消除公表達(dá)式、常量傳播呀枢、基本塊重排序等胚股;還會(huì)一些與Java語言特性密切相關(guān)的優(yōu)化技術(shù),如范圍檢查消除裙秋、空值檢查消除等琅拌;另外缨伊,還進(jìn)行一些不穩(wěn)定的激進(jìn)優(yōu)化,如守護(hù)內(nèi)聯(lián)进宝、分支頻率預(yù)測(cè)等刻坊。

(C)收集性能信息
由于C2會(huì)收集程序運(yùn)行信息,因此其優(yōu)化范圍更多在于全局優(yōu)化党晋,不僅僅是一個(gè)方塊的優(yōu)化谭胚;收集的信息主要有:分支的跳轉(zhuǎn)/不跳轉(zhuǎn)的頻率、某條指令上出現(xiàn)過的類型未玻、是否出現(xiàn)過空值灾而、是否出現(xiàn)過異常等。

(D)與C1的不同點(diǎn)
和C1的不同主要在于寄存器分配策略及優(yōu)化范圍扳剿,寄存器分配策略上C2采用傳統(tǒng)的全局圖著色寄存器分配算法旁趟;C2編譯速度較為緩慢,但遠(yuǎn)遠(yuǎn)超過傳統(tǒng)的靜態(tài)優(yōu)化編譯器庇绽;而且編譯輸出的代碼質(zhì)量高锡搜,可以減少本地代碼的執(zhí)行時(shí)間。

(E)設(shè)置參數(shù)
可以使用"-server"參數(shù)強(qiáng)制選擇運(yùn)行在Server模式(Server VM)

七.HotSpot 虛擬機(jī)的優(yōu)化技術(shù)

1.HotSpot 虛擬機(jī)使用了很多種優(yōu)化技術(shù)敛劝,這里只簡(jiǎn)單介紹其中的幾種余爆,完整的優(yōu)化技術(shù)介紹可以參考官網(wǎng)內(nèi)容。

(1)公共子表達(dá)式消除:
如果一個(gè)表達(dá)式已經(jīng)進(jìn)行過計(jì)算夸盟,并且在下次用到之前依賴的變量沒有變化蛾方,即表達(dá)式的計(jì)算結(jié)果不會(huì)發(fā)生變化,則在下次使用這個(gè)表達(dá)式時(shí)直接使用計(jì)算的結(jié)果上陕。

(2)數(shù)組邊界檢查消除:
在 Java 中訪問數(shù)組時(shí)桩砰,會(huì)自動(dòng)進(jìn)行邊界檢查來防止數(shù)組下標(biāo)越界。但是對(duì)于某些情況并不需要每次訪問都去檢查释簿,如在一個(gè)循環(huán)中遍歷數(shù)組元素亚隅,如果虛擬機(jī)能夠確定下標(biāo)不會(huì)發(fā)生越界并且優(yōu)化確實(shí)能夠提高運(yùn)行速度,則虛擬機(jī)會(huì)去除每次訪問的下標(biāo)檢查庶溶。

(3)方法內(nèi)聯(lián):
對(duì)于可以內(nèi)聯(lián)的方法煮纵,直接復(fù)制到調(diào)用者代碼中,減少方法調(diào)用次數(shù)和性能消耗偏螺。

(4)逃逸分析:
方法中定義的一個(gè)對(duì)象行疏,如果會(huì)被其他方法訪問則稱為方法逃逸,如果會(huì)被其他線程訪問則稱為線程逃逸套像。對(duì)于不能逃逸的對(duì)象酿联,HotSpot 虛擬機(jī)采用了棧上分配、同步消除、標(biāo)量替換等方法進(jìn)行優(yōu)化贞让。

八.JIT編譯閾值

即時(shí)編譯JIT只在代碼段執(zhí)行足夠次數(shù)才會(huì)進(jìn)行優(yōu)化周崭,在執(zhí)行過程中不斷收集各種數(shù)據(jù),作為優(yōu)化的決策喳张,所以在優(yōu)化完成之前续镇,例子中的User對(duì)象還是在堆上進(jìn)行分配。

那么一段代碼需要執(zhí)行多少次才會(huì)觸發(fā)JIT優(yōu)化呢蹲姐?通常這個(gè)值由-XX:CompileThreshold參數(shù)進(jìn)行設(shè)置:

1磨取、使用client編譯器時(shí)人柿,默認(rèn)為1500柴墩;
2、使用server編譯器時(shí)凫岖,默認(rèn)為10000江咳;

意味著如果方法調(diào)用次數(shù)或循環(huán)次數(shù)達(dá)到這個(gè)閾值就會(huì)觸發(fā)標(biāo)準(zhǔn)編譯,更改CompileThreshold標(biāo)志的值哥放,將使編譯器提早(或延遲)編譯歼指。

除了標(biāo)準(zhǔn)編譯,還有一個(gè)叫做OSR(On Stack Replacement)棧上替換的編譯甥雕,如上述例子中的main方法踩身,只執(zhí)行一次,遠(yuǎn)遠(yuǎn)達(dá)不到閾值社露,但是方法體中執(zhí)行了多次循環(huán)挟阻,OSR編譯就是只編譯該循環(huán)代碼,然后將其替換峭弟,下次循環(huán)時(shí)就執(zhí)行編譯好的代碼附鸽,不過觸發(fā)OSR編譯也需要一個(gè)閾值,可以通過以下公式得到瞒瘸。

-XX:CompileThreshold = 10000
-XX:OnStackReplacePercentage = 140
-XX:InterpreterProfilePercentage = 33
OSR trigger = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)) / 100 = 10700

其中trigger即為OSR編譯的閾值坷备。

更多關(guān)于編譯閾值信息請(qǐng)參考http://www.reibang.com/p/20bd2e9b1f03

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市情臭,隨后出現(xiàn)的幾起案子省撑,更是在濱河造成了極大的恐慌,老刑警劉巖俯在,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟秫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朝巫,警方通過查閱死者的電腦和手機(jī)鸿摇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劈猿,“玉大人拙吉,你說我怎么就攤上這事潮孽。” “怎么了筷黔?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵往史,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我佛舱,道長(zhǎng)椎例,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任请祖,我火速辦了婚禮订歪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肆捕。我一直安慰自己刷晋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布慎陵。 她就那樣靜靜地躺著眼虱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪席纽。 梳的紋絲不亂的頭發(fā)上捏悬,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音润梯,去河邊找鬼过牙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仆救,可吹牛的內(nèi)容都是我干的抒和。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼彤蔽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼摧莽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起顿痪,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤镊辕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蚁袭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體征懈,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年揩悄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卖哎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亏娜,靈堂內(nèi)的尸體忽然破棺而出焕窝,到底是詐尸還是另有隱情,我是刑警寧澤维贺,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布它掂,位于F島的核電站,受9級(jí)特大地震影響溯泣,放射性物質(zhì)發(fā)生泄漏虐秋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一垃沦、第九天 我趴在偏房一處隱蔽的房頂上張望客给。 院中可真熱鬧,春花似錦栏尚、人聲如沸起愈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至官觅,卻和暖如春纵菌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背休涤。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工咱圆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人功氨。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓序苏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捷凄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忱详,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容