JIT - 即時(shí)編譯優(yōu)化技術(shù)

JVM Client 模式和 Server模式的區(qū)別

JVM 運(yùn)行模式

通過 java -version 可查看 JVM 所處的模式夹纫,并可以通過修改配置文件進(jìn)行配置逗柴,那它們有什么區(qū)別呢?

Server:-Server 模式啟動時(shí)视译,速度較慢院水,但是啟動之后,性能更高屋摔,適合運(yùn)行服務(wù)器后臺程序

Client:-Client 模式啟動時(shí)烁设,速度較快,啟動之后不如 Server钓试,適合用于桌面等有界面的程序

熱點(diǎn)代碼

理解

當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁時(shí)装黑,就會把這些代碼認(rèn)定為“熱點(diǎn)代碼”。

熱點(diǎn)代碼的分類

被多次調(diào)用的方法

一個(gè)方法被調(diào)用得多了弓熏,方法體內(nèi)代碼執(zhí)行的次數(shù)自然就多恋谭,成為“熱點(diǎn)代碼”是理所當(dāng)然的。

被多次執(zhí)行的循環(huán)體

一個(gè)方法只被調(diào)用過一次或少量的幾次挽鞠,但是方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體疚颊,這樣循環(huán)體的代碼也被重復(fù)執(zhí)行多次,因此這些代碼也應(yīng)該認(rèn)為是“熱點(diǎn)代碼”信认。

上面提到的多次是一個(gè)不具體的詞語材义,那到底是多少次才能成為熱點(diǎn)代碼呢?

如何檢測熱點(diǎn)代碼

判斷一段代碼是否是熱點(diǎn)代碼狮杨,是否需要觸發(fā)即使編譯母截,這樣的行為稱為熱點(diǎn)探測,熱點(diǎn)探測并不一定知道方法具體被調(diào)用了多少次橄教,目前主要的熱點(diǎn)探測判定方式有兩種:

基于采樣的熱點(diǎn)探測:采用這種方法的虛擬機(jī)會周期性地檢查各個(gè)線程的棧頂如果發(fā)現(xiàn)某個(gè)(或某些)方法經(jīng)常出現(xiàn)在棧頂清寇,那這個(gè)方法就是“熱點(diǎn)方法”

優(yōu)點(diǎn):實(shí)現(xiàn)簡單高效,容易獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開即可)

缺點(diǎn):不精確护蝶,容易因?yàn)橐驗(yàn)槭艿骄€程阻塞或別的外界因素的影響而擾亂熱點(diǎn)探測

基于計(jì)數(shù)器的熱點(diǎn)探測:采用這種方法的虛擬機(jī)會為每個(gè)方法(甚至是代碼塊)建立計(jì)數(shù)器华烟,統(tǒng)計(jì)方法的執(zhí)行次數(shù),如果次數(shù)超過一定的閾值就認(rèn)為它是“熱點(diǎn)方法”

優(yōu)點(diǎn):統(tǒng)計(jì)結(jié)果精確嚴(yán)謹(jǐn)

缺點(diǎn):實(shí)現(xiàn)麻煩持灰,需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器盔夜,不能直接獲取到方法的調(diào)用關(guān)系

HotSpot使用第二種 - 基于計(jì)數(shù)器的熱點(diǎn)探測方法。

確定了檢測熱點(diǎn)代碼的方式堤魁,如何計(jì)算具體的次數(shù)呢喂链?

計(jì)數(shù)器的種類(兩種共同協(xié)作)

方法調(diào)用計(jì)數(shù)器:這個(gè)計(jì)數(shù)器用于統(tǒng)計(jì)方法被調(diào)用的次數(shù)。默認(rèn)閾值在 Client 模式下是 1500 次妥泉,在 Server 模式下是 10000 次

回邊計(jì)數(shù)器:統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù)

了解了熱點(diǎn)代碼和計(jì)數(shù)器有什么用呢椭微?達(dá)到計(jì)數(shù)器的閾值會觸發(fā)后文講解的即時(shí)編譯,也就是說即時(shí)編譯是需要達(dá)到某種條件才會觸發(fā)的盲链,先寫結(jié)論蝇率,后文講解什么是即時(shí)編譯器迟杂。

兩個(gè)計(jì)數(shù)器的協(xié)作(這里討論的是方法調(diào)用計(jì)數(shù)器的情況):當(dāng)一個(gè)方法被調(diào)用時(shí),會先檢查該方法是否存在被 JIT(后文講解) 編譯過的版本本慕,如果存在排拷,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在已被編譯過的版本锅尘,則將此方法的調(diào)用計(jì)數(shù)器加 1监氢,然后判斷方法調(diào)用計(jì)數(shù)器與回邊計(jì)數(shù)器之和是否超過方法調(diào)用計(jì)數(shù)器的閾值。如果已經(jīng)超過閾值藤违,那么將會向即時(shí)編譯器提交一個(gè)該方法的代碼編譯請求忙菠。

當(dāng)編譯工作完成之后,這個(gè)方法的調(diào)用入口地址就會被系統(tǒng)自動改成新的纺弊,下一次調(diào)用該方法時(shí)就會使用已編譯的版本。

什么是字節(jié)碼骡男、機(jī)器碼淆游、本地代碼?

字節(jié)碼是指平常所了解的 .class 文件隔盛,Java 代碼通過 javac 命令編譯成字節(jié)碼

機(jī)器碼和本地代碼都是指機(jī)器可以直接識別運(yùn)行的代碼犹菱,也就是機(jī)器指令

字節(jié)碼是不能直接運(yùn)行的,需要經(jīng)過 JVM 解釋或編譯成機(jī)器碼才能運(yùn)行

此時(shí)你要問了吮炕,為什么 Java 不直接編譯成機(jī)器碼腊脱,這樣不是更快嗎?

1. 機(jī)器碼是與平臺相關(guān)的龙亲,也就是操作系統(tǒng)相關(guān)陕凹,不同操作系統(tǒng)能識別的機(jī)器碼不同,如果編譯成機(jī)器碼那豈不是和 C鳄炉、C++差不多了杜耙,不能跨平臺,Java 就沒有那響亮的口號 “一次編譯拂盯,到處運(yùn)行”佑女;

2.之所以不一次性全部編譯,是因?yàn)橛幸恍┐a只運(yùn)行一次谈竿,沒必要編譯团驱,直接解釋運(yùn)行就可以。而那些“熱點(diǎn)”代碼空凸,反復(fù)解釋執(zhí)行肯定很慢嚎花,JVM 在運(yùn)行程序的過程中不斷優(yōu)化,用JIT編譯器編譯那些熱點(diǎn)代碼劫恒,讓他們不用每次都逐句解釋執(zhí)行贩幻;

3.還有一方面的原因是后文講解的解釋器與編譯器共存的原因轿腺。

什么是 JIT ?

為了提高熱點(diǎn)代碼的執(zhí)行效率丛楚,在運(yùn)行時(shí)族壳,虛擬機(jī)將會把這些代碼編譯成與本地平臺相關(guān)的機(jī)器碼,并進(jìn)行各種層次的優(yōu)化趣些,完成這個(gè)任務(wù)的編譯器稱為即時(shí)編譯器(Just In Time Compiler)仿荆,簡稱 JIT 編譯器

什么是編譯和解釋?

編譯器:把源程序的每一條語句都編譯成機(jī)器語言,并保存成二進(jìn)制文件,這樣運(yùn)行時(shí)計(jì)算機(jī)可以直接以機(jī)器語言來運(yùn)行此程序,速度很快;

解釋器:只在執(zhí)行程序時(shí),才一條一條的解釋成機(jī)器語言給計(jì)算機(jī)來執(zhí)行,所以運(yùn)行速度是不如編譯后的程序運(yùn)行的快的坏平;

通過javac命令將 Java 程序的源代碼編譯成 Java 字節(jié)碼拢操,即我們常說的 class 文件。這是我們通常意義上理解的編譯舶替。

字節(jié)碼并不是機(jī)器語言令境,要想讓機(jī)器能夠執(zhí)行,還需要把字節(jié)碼翻譯成機(jī)器指令顾瞪。這個(gè)過程是Java 虛擬機(jī)做的舔庶,這個(gè)過程也叫編譯。是更深層次的編譯陈醒。(實(shí)際上就是解釋惕橙,引入 JIT 之后也存在編譯)

此時(shí)又有疑惑了,Java 不是解釋執(zhí)行的嗎钉跷?

沒錯(cuò)弥鹦,Java 需要將字節(jié)碼逐條翻譯成對應(yīng)的機(jī)器指令并且執(zhí)行,這就是傳統(tǒng)的 JVM 的解釋器的功能爷辙,正是由于解釋器逐條翻譯并執(zhí)行這個(gè)過程的效率低彬坏,引入了 JIT 即時(shí)編譯技術(shù)。

必須指出的是膝晾,不管是解釋執(zhí)行苍鲜,還是編譯執(zhí)行,最終執(zhí)行的代碼單元都是可直接在真實(shí)機(jī)器上運(yùn)行的機(jī)器碼玷犹,或稱為本地代碼

附一張圖來理解


為何 HotSpot 虛擬機(jī)要使用解釋器與編譯器并存的架構(gòu)混滔?

解釋器與編譯器兩者各有優(yōu)勢

解釋器:當(dāng)程序需要迅速啟動和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用歹颓,省去編譯的時(shí)間坯屿,立即執(zhí)行。

編譯器:在程序運(yùn)行后巍扛,隨著時(shí)間的推移领跛,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后撤奸,可以獲取更高的執(zhí)行效率吠昭。

兩者的協(xié)作:在程序運(yùn)行環(huán)境中內(nèi)存資源限制較大時(shí)喊括,可以使用解釋執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來提升效率矢棚。當(dāng)通過編譯器優(yōu)化時(shí)郑什,發(fā)現(xiàn)并沒有起到優(yōu)化作用,蒲肋,可以通過逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行蘑拯。

即時(shí)編譯器與 Java 虛擬機(jī)的關(guān)系

即時(shí)編譯器并不是虛擬機(jī)必需的部分,Java 虛擬機(jī)規(guī)范并沒有規(guī)定 Java 虛擬機(jī)內(nèi)必須要有即時(shí)編譯器的存在兜粘,更沒有限定或指導(dǎo)即時(shí)編譯器應(yīng)該如何去實(shí)現(xiàn)申窘。

但是,即時(shí)編譯器編譯性能的好壞孔轴、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機(jī)優(yōu)秀與否的最關(guān)鍵的指標(biāo)之一剃法。它也是虛擬機(jī)中最核心且最能體現(xiàn)虛擬機(jī)技術(shù)水平的部分。

即時(shí)編譯器的分類

Client Compiler - C1編譯器

Server Compiler - C2編譯器

目前主流的 HotSpot 虛擬機(jī)(JDK1.7 及之前版本的虛擬機(jī))默認(rèn)采用一個(gè)解釋器和其中一個(gè)編譯器直接配合的方式工作路鹰,程序使用哪個(gè)編譯器玄窝,取決于虛擬機(jī)運(yùn)行的模式,就是文章開頭提到的兩種模式悍引。

在 HotSpot 中,解釋器和 JIT 即時(shí)編譯器是同時(shí)存在的帽氓,他們是 JVM 的兩個(gè)組件趣斤。對于不同類型的應(yīng)用程序,用戶可以根據(jù)自身的特點(diǎn)和需求黎休,靈活選擇是基于解釋器運(yùn)行還是基于 JIT 編譯器運(yùn)行浓领。HotSpot 為用戶提供了幾種運(yùn)行模式供選擇,可通過參數(shù)設(shè)定势腮,分別為:解釋模式联贩、編譯模式、混合模式捎拯,HotSpot 默認(rèn)是混合模式泪幌,需要注意的是編譯模式并不是完全通過 JIT 進(jìn)行編譯,只是優(yōu)先采用編譯方式執(zhí)行程序署照,但是解釋器仍然要在編譯無法進(jìn)行的情況下介入執(zhí)行過程祸泪。

分層編譯

產(chǎn)生的原因:由于即時(shí)編譯器編譯本地代碼需要占用程序運(yùn)行時(shí)間,要編譯出優(yōu)化程度更高的代碼建芙,所花費(fèi)的時(shí)間可能更長没隘;而且要想編譯出優(yōu)化程度更高的代碼,解釋器可能還要替編譯器收集性能監(jiān)控信息禁荸,這對解釋執(zhí)行的速度也有影響右蒲。為了在程序啟動響應(yīng)速度與運(yùn)行效率之間達(dá)到最佳平衡阀湿,HotSpot 虛擬機(jī)啟用分層編譯的策略

分層編譯根據(jù)編譯器編譯、優(yōu)化的規(guī)模與耗時(shí)瑰妄,劃分出不同的編譯層次:

第 0 層:程序解釋執(zhí)行陷嘴,解釋器不開啟性能監(jiān)控功能,可觸發(fā)第 1 層編譯翰撑。

第 1 層:也稱為 C1 編譯罩旋,將字節(jié)碼編譯為本地代碼,進(jìn)行簡單眶诈,可靠的優(yōu)化涨醋,如有必要將加入性能監(jiān)控的邏輯。

第 2 層(或 2 層以上):也稱為 C2 編譯逝撬,也是將字節(jié)碼編譯為本地代碼浴骂,但是會啟用一些編譯耗時(shí)較長的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化宪潮。

實(shí)施分層編譯后溯警,Client Compiler 和 Server Compiler 將會同時(shí)工作,許多代碼都可能會被多次編譯看狡相,用 Client Compiler 獲取更高的編譯速度梯轻,用 Server Compiler 獲取更好的編譯質(zhì)量,在解釋執(zhí)行的時(shí)候也無須再承擔(dān)收集性能監(jiān)控信息的任務(wù)尽棕。

編譯優(yōu)化技術(shù)

Java 程序員有一個(gè)共識喳挑,以編譯方式執(zhí)行本地代碼比解釋執(zhí)行方式更快,之所以有這樣的共識滔悉,除去虛擬機(jī)解釋執(zhí)行字節(jié)碼時(shí)額外消耗時(shí)間的原因外伊诵,還有一個(gè)重要的原因就是虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)幾乎把對代碼的所有優(yōu)化措施都集中在了即時(shí)編譯器中,因此一般來說回官,即時(shí)編譯器產(chǎn)生的本地代碼會比 javac 產(chǎn)生的字節(jié)碼更優(yōu)秀曹宴。以下是具有代表性的 HotSpot 虛擬機(jī)的即時(shí)編譯器在生成代碼時(shí)采用的代碼優(yōu)化技術(shù):

- 語言無關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達(dá)式消除

如果一個(gè)表達(dá)式 E 已經(jīng)計(jì)算過了,并且從先前的計(jì)算到現(xiàn)在 E 中所有變量的值都沒有發(fā)生變化歉提,那么 E 的這次出現(xiàn)就成為了公共子表達(dá)式笛坦。對于這種表達(dá)式,沒必要花時(shí)間再對它進(jìn)行計(jì)算苔巨,只需要直接使用前面計(jì)算過的表達(dá)式結(jié)果代替 E 就可以了弯屈。例子:int d = (c*b) * 12 + a + (a+ b * c) ->?int d = E * 12 + a + (a+ E)

- 語言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一:數(shù)組范圍檢查消除

在 Java 語言中訪問數(shù)組元素的時(shí)候系統(tǒng)將會自動進(jìn)行上下界的范圍檢查,超出邊界會拋出異常恋拷。對于虛擬機(jī)的執(zhí)行子系統(tǒng)來說资厉,每次數(shù)組元素的讀寫都帶有一次隱含的條件判定操作,對于擁有大量數(shù)組訪問的程序代碼蔬顾,這無疑是一種性能負(fù)擔(dān)宴偿。Java 在編譯期根據(jù)數(shù)據(jù)流分析可以判定范圍進(jìn)而消除上下界檢查湘捎,節(jié)省多次的條件判斷操作。

- 最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián)

簡單的理解為把目標(biāo)方法的代碼“復(fù)制”到發(fā)起調(diào)用的方法中窄刘,消除一些無用的代碼窥妇。只是實(shí)際的 JVM 中的內(nèi)聯(lián)過程很復(fù)雜,在此不分析娩践。

- 最前沿的優(yōu)化技術(shù)之一:逃逸分析

逃逸分析的基本行為就是分析對象動態(tài)作用域:當(dāng)一個(gè)對象在方法中杯定義后活翩,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中翻伺,稱為方法逃逸材泄。甚至可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實(shí)例變量吨岭,稱為線程逃逸拉宗。

如果能證明一個(gè)對象不會逃逸到方法或線程之外,也就是別的方法或線程無法通過任何途徑訪問到這個(gè)對象辣辫,則可以為這個(gè)變量進(jìn)行一些高效的優(yōu)化:

- 棧上分配:將不會逃逸的局部對象分配到棧上旦事,那對象就會隨著方法的結(jié)束而自動銷毀,減少垃圾收集系統(tǒng)的壓力急灭。

- 同步消除:如果該變量不會發(fā)生線程逃逸姐浮,也就是無法被其他線程訪問,那么對這個(gè)變量的讀寫就不存在競爭葬馋,可以將同步措施消除掉(同步是需要付出代價(jià)的)

- 標(biāo)量替換:標(biāo)量是指無法在分解的數(shù)據(jù)類型卖鲤,比如原始數(shù)據(jù)類型以及reference類型。而聚合量就是可繼續(xù)分解的点楼,比如 Java 中的對象。標(biāo)量替換如果一個(gè)對象不會被外部訪問白对,并且對象可以被拆散的話掠廓,真正執(zhí)行時(shí)可能不創(chuàng)建這個(gè)對象,而是直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來代替甩恼。這種方式不僅可以讓對象的成員變量在棧上分配和讀寫蟀瞧,還可以為后后續(xù)進(jìn)一步的優(yōu)化手段創(chuàng)建條件。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末条摸,一起剝皮案震驚了整個(gè)濱河市悦污,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钉蒲,老刑警劉巖切端,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異顷啼,居然都是意外死亡踏枣,警方通過查閱死者的電腦和手機(jī)昌屉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茵瀑,“玉大人间驮,你說我怎么就攤上這事÷碜颍” “怎么了竞帽?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸿捧。 經(jīng)常有香客問我屹篓,道長,這世上最難降的妖魔是什么笛谦? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任抱虐,我火速辦了婚禮,結(jié)果婚禮上饥脑,老公的妹妹穿的比我還像新娘恳邀。我一直安慰自己,他們只是感情好灶轰,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布谣沸。 她就那樣靜靜地躺著,像睡著了一般笋颤。 火紅的嫁衣襯著肌膚如雪乳附。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天伴澄,我揣著相機(jī)與錄音赋除,去河邊找鬼。 笑死非凌,一個(gè)胖子當(dāng)著我的面吹牛举农,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敞嗡,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼颁糟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喉悴?” 一聲冷哼從身側(cè)響起棱貌,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箕肃,沒想到半個(gè)月后婚脱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年起惕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涡贱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惹想,死狀恐怖问词,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘀粱,我是刑警寧澤激挪,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站锋叨,受9級特大地震影響垄分,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娃磺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一薄湿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偷卧,春花似錦豺瘤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晌梨,卻和暖如春桥嗤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仔蝌。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工泛领, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敛惊。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓渊鞋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親豆混。 傳聞我的和親對象是個(gè)殘疾皇子篓像,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

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