JVM運(yùn)行期優(yōu)化

一、前言

JVM運(yùn)行期的優(yōu)化主要是指程序在編譯成字節(jié)碼之后速挑,JVM通過解釋器去解釋執(zhí)行拘悦,再針對(duì)程序運(yùn)行的資源占用等情況進(jìn)行分析然后做出的一系列的優(yōu)化。Java程序的效率之所以較高(即使是和接近底層的c/c++語言相比較仆救,在Java內(nèi)部的即時(shí)編譯器優(yōu)化的情況下,很多應(yīng)用場(chǎng)景下效率也毫不遜色)矫渔,是離不開JVM對(duì)程序進(jìn)行的優(yōu)化的彤蔽,這篇博客就來總結(jié)一下虛擬機(jī)在背后給我們做的工作(針對(duì)的是目前市面上主流的HotSpot虛擬機(jī)而言)。

二庙洼、JVM的即時(shí)編譯器

1.解釋器與編譯器

JVM虛擬機(jī)采用的是解釋器與編譯器共存的架構(gòu)顿痪,這樣的搭配平均情況下能最好的發(fā)揮程序的性能。解釋器與編譯器各有優(yōu)勢(shì):當(dāng)程序需要迅速啟動(dòng)和快速執(zhí)行的時(shí)候油够,解釋器可以首先發(fā)揮作用员魏,省去編譯的時(shí)間,能夠立即執(zhí)行叠聋;而程序運(yùn)行一段時(shí)間后撕阎,即時(shí)編譯器(JIT)開始發(fā)揮作用,會(huì)根據(jù)程序代碼的運(yùn)行狀況碌补,把越來越多的代碼編譯成本地代碼虏束,以提高執(zhí)行的效率。當(dāng)程序在運(yùn)行環(huán)境中占用內(nèi)存資源較多時(shí)厦章,可以使用解釋器執(zhí)行來減少內(nèi)存的占用率镇匀。同時(shí),當(dāng)編譯器進(jìn)行過早優(yōu)化時(shí)袜啃,解釋器可以讓編譯器根據(jù)概率來選擇一些大多數(shù)情況下都能提高運(yùn)行速度的優(yōu)化手段汗侵,但如果這次優(yōu)化后,導(dǎo)致后續(xù)程序的運(yùn)行出現(xiàn)特殊狀況群发,這時(shí)即時(shí)編譯器又會(huì)通過逆優(yōu)化來回退到解釋器執(zhí)行晰韵。整個(gè)過程也就是下圖所示

20170601114304750.png

從上圖也可以看出,編譯器里面內(nèi)置了Client和Server兩個(gè)編譯器(通常也被稱為c1和c2編譯器)熟妓,JVM采用了分層編譯的方式來使程序啟動(dòng)的響應(yīng)速度與運(yùn)行效率之間達(dá)到平衡:

1.第0層雪猪,程序解釋執(zhí)行,解釋器不開啟性能監(jiān)控起愈,可觸發(fā)第一層編譯只恨。
2.第1層译仗,c1編譯,將字節(jié)碼編譯為本地代碼官觅,進(jìn)行簡(jiǎn)單可靠的優(yōu)化纵菌。
3.第2層,c2編譯休涤,將字節(jié)碼編譯為本地代碼咱圆,但是會(huì)進(jìn)行一些編譯耗時(shí)較長的優(yōu)化。

2.編譯對(duì)象與觸發(fā)條件

在JVM運(yùn)行過程中會(huì)被即使編譯器編譯的主要有被多次調(diào)用的方法和被多次執(zhí)行的循環(huán)體這些熱點(diǎn)代碼滑绒。這里需要注意,對(duì)于第一種情況隘膘,直接理所當(dāng)然的以整個(gè)方法作為JIT編譯的對(duì)象疑故,而對(duì)于后面一種情況,盡管編譯動(dòng)作是由循環(huán)體觸發(fā)的弯菊,編譯器還是會(huì)以它所在的整個(gè)方法為編譯對(duì)象纵势。這種編譯是發(fā)生在方法執(zhí)行的過程中,因此也被稱為棧上替換(OSR管钳,方法棧幀還在棧上钦铁,方法就被替換了)

熱點(diǎn)代碼的判定方式

在現(xiàn)在的虛擬機(jī)中才漆,主要有以下幾種方式:

1.基于采樣的熱點(diǎn)探測(cè):采用這種方法的虛擬機(jī)會(huì)周期性地檢查各個(gè)線程的棧頂牛曹,如果發(fā)現(xiàn)某個(gè)方法經(jīng)常出現(xiàn)在棧頂,這個(gè)方法就是熱點(diǎn)方法醇滥。這種方法實(shí)現(xiàn)起來較為簡(jiǎn)單黎比,可以很容易的獲取方法調(diào)用的關(guān)系,缺點(diǎn)是由于有線程阻塞或別的因素影響鸳玩,無法精確的對(duì)熱點(diǎn)進(jìn)行探測(cè)阅虫。
2.基于計(jì)數(shù)器的熱點(diǎn)探測(cè):采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法(甚至是代碼塊)建立并維護(hù)計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù)不跟,執(zhí)行次數(shù)超過一定的閥值就會(huì)認(rèn)為它是熱點(diǎn)方法颓帝,這種方式更加精確和嚴(yán)謹(jǐn),但統(tǒng)計(jì)時(shí)需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器窝革,而且不能獲取方法的調(diào)用關(guān)系购城,實(shí)現(xiàn)起來較為麻煩。
3.基于蹤跡(Trace)的熱點(diǎn)探測(cè):采用這種方式的虛擬機(jī)是將一段頻繁執(zhí)行的代碼作為一個(gè)編譯單元虐译,并僅對(duì)該代碼片段進(jìn)行編譯工猜,該代碼片段由一個(gè)線性且連續(xù)的指令序列組成,僅有一個(gè)入口菱蔬,但有多個(gè)出口篷帅。也就是說史侣,基于蹤跡而編譯的熱點(diǎn)代碼不僅僅局限在一個(gè)單獨(dú)的方法或者代碼快中,一條Trace可能對(duì)應(yīng)多個(gè)方法魏身,代碼中頻繁執(zhí)行的路徑就可能被識(shí)別成不同的蹤跡惊橱。因此這種方法有著更高的精度,并且能夠避免編譯不是頻繁執(zhí)行的代碼箭昵,減少不必要的編譯開銷税朴,但這種方法的實(shí)現(xiàn)就更為的復(fù)雜。在Android早期的Dalvik虛擬機(jī)的JIT編譯器就是使用的這種方式(從Android4.4開始家制,Google就把Android中的Dalvik虛擬機(jī)無縫切換到了ART虛擬機(jī)正林,這里簡(jiǎn)單的說一下,Android上面的虛擬機(jī)是按照J(rèn)VM的部分規(guī)范去實(shí)現(xiàn)的一種類似的東西颤殴,并不屬于Java虛擬機(jī)觅廓,并且與JVM最大的不同是就是Dalvik/ART基于寄存器,而JVM基于棧涵但,Android里面是每個(gè)程序都對(duì)應(yīng)著一個(gè)單獨(dú)的虛擬機(jī)杈绸,也是一個(gè)單獨(dú)的進(jìn)程)。


而在JVM虛擬機(jī)中使用的是基于計(jì)數(shù)器的熱點(diǎn)探測(cè)矮瘟,至于為什么不使用基于蹤跡的熱點(diǎn)探測(cè)瞳脓,我想一個(gè)是實(shí)現(xiàn)上的困難,并且基于棧的JVM與寄存器的執(zhí)行速度無法相比澈侠,繁瑣的優(yōu)化反而會(huì)造成得不償失的局面劫侧。這種方式又包含了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器

方法掉用計(jì)數(shù)器:統(tǒng)計(jì)一段時(shí)間內(nèi)方法被調(diào)用的次數(shù)哨啃,當(dāng)超過一個(gè)時(shí)間限度板辽,它的調(diào)用次數(shù)仍然不足以給JIT編器編譯,這個(gè)方法的調(diào)用計(jì)數(shù)久會(huì)減半棘催。
回邊計(jì)數(shù)器:主要是統(tǒng)計(jì)循環(huán)體內(nèi)的代碼執(zhí)行的次數(shù)劲弦,在字節(jié)碼遇到控制流后向后跳轉(zhuǎn)的指令稱為回邊,建立回邊計(jì)數(shù)器也是為了觸發(fā)OSR醇坝。因?yàn)橛行┣闆r下邑跪,比如空的循環(huán),照樣會(huì)執(zhí)行對(duì)應(yīng)的次數(shù)呼猪,但它是直接跳轉(zhuǎn)到自己画畅,所以JIT編譯器去編譯這種代碼是沒有任何意義的。

3.編譯過程

第一階段宋距,首先在字節(jié)碼層做一些系列的優(yōu)化轴踱,如方法內(nèi)聯(lián)、常量傳播等谚赎,然后將字節(jié)碼構(gòu)造成一種高級(jí)中間代碼(HIR)淫僻,HIR使用靜態(tài)單分配(根據(jù)調(diào)用的方法和它接收的參數(shù))的形式來代表代碼值诱篷,使得構(gòu)造HIR時(shí)的優(yōu)化更加容易實(shí)現(xiàn)。
第二階段雳灵,對(duì)傳來的HIR進(jìn)行空值檢查棕所、范圍檢查消除等優(yōu)化,然后從HIR中產(chǎn)生低級(jí)中間代碼(LIR)悯辙。
最后階段:使用線性掃描算法琳省,在LIR上分配寄存器,并在LIR上做窺孔優(yōu)化(局部的優(yōu)化方式躲撰,編譯器僅僅在一個(gè)或者多個(gè)基本塊中针贬,針對(duì)已經(jīng)生成的代碼,結(jié)合CPU自己指令的特點(diǎn)拢蛋,通過一些認(rèn)為可能帶來性能提升的轉(zhuǎn)換規(guī)則桦他,或者通過整體的分析,通過指令轉(zhuǎn)換瓤狐,提升代碼性能)瞬铸,然后產(chǎn)生機(jī)器代碼批幌。

整個(gè)大致的過程如下圖所示


1688022-2ca3e27ef583f275.png

三础锐、編譯優(yōu)化技術(shù)

JVM團(tuán)隊(duì)幾乎把所有代碼優(yōu)化的措施集中在了即時(shí)編譯器中,所以即時(shí)編譯器產(chǎn)生的本地代碼要比原來的解釋器解釋執(zhí)行字節(jié)碼所產(chǎn)生的本地代碼要更優(yōu)荧缘。優(yōu)化的技術(shù)非常的多皆警,這里就不做過多的描述了。重點(diǎn)整理一下JVM中比較前沿的逃逸分析截粗。
逃逸分析并不是直接優(yōu)化代碼的手段信姓,而是為其它優(yōu)化方式提供的分析技術(shù)。逃逸分析的基本行為就是分析對(duì)象的動(dòng)態(tài)作用域(當(dāng)一個(gè)對(duì)象在方法中被定義之后绸罗,可能被外部方法所引用意推,例如作為參數(shù)傳遞到其他方法中,稱為方法逃逸珊蟀;可能被外部線程訪問到菊值,比如賦值給類變量或可以在其它線程中訪問的實(shí)例變量,稱為線程逃逸)育灸。如果別的方法或線程無法通過任何途徑訪問到這個(gè)對(duì)象腻窒,就能為這個(gè)對(duì)象下列高效的優(yōu)化。

1.棧上分配

在JVM中磅崭,創(chuàng)建對(duì)象所需的內(nèi)存是從Java堆上分配出來的儿子,而Java堆中的對(duì)象對(duì)各個(gè)線程都是共享和可見的,只要持有這個(gè)對(duì)象的引用砸喻,就能獲取Java堆中所儲(chǔ)存的該對(duì)象的數(shù)據(jù)柔逼,JVM中的垃圾收集器可以回收堆中不再使用的對(duì)象蒋譬,但是篩選可回收對(duì)象以及回收和整理內(nèi)存都需要比較大的時(shí)間開銷。如果能確定一個(gè)對(duì)象不會(huì)出現(xiàn)方法逃逸的情況卒落,就可以直接在棧上分配對(duì)象的內(nèi)存羡铲,對(duì)象隨著棧幀出棧而消耗,這樣一來可以很大程度的減少垃圾收集系統(tǒng)的壓力儡毕。

2.同步消除

如果確定一個(gè)對(duì)象不會(huì)出現(xiàn)線程逃逸也切,也就是不會(huì)被其它線程訪問到,那么對(duì)這個(gè)對(duì)象的的讀寫操作肯定不會(huì)出現(xiàn)競(jìng)爭(zhēng)腰湾,就可以直接去除同步的措施雷恃,線程的同步操作也是一個(gè)比較耗時(shí)的操作,

3.標(biāo)量替換

標(biāo)量是指一個(gè)數(shù)據(jù)無法被分解成更小的數(shù)據(jù)來表示了费坊,例如Java中的原始數(shù)據(jù)類型就是這樣倒槐。相反,聚合量就表示一個(gè)能繼續(xù)分解的數(shù)據(jù)附井,Java中的對(duì)象就是如此讨越。所以如果逃逸分析證明了一個(gè)對(duì)象不會(huì)被外部訪問,那么就可以根據(jù)程序訪問的情況永毅,把對(duì)象拆分把跨,然后把里面用到的成員變量替換成原始類型,這種方式就被稱為標(biāo)量替換沼死。因?yàn)闃?biāo)量也是直接存儲(chǔ)在棧上面的着逐,而棧上儲(chǔ)存的數(shù)據(jù)有大概率會(huì)被分配到CPU的寄存器上,并且存儲(chǔ)到棧上也方便后續(xù)的優(yōu)化操作意蛀。
逃逸分析也是一種比較重要的間接優(yōu)化手段耸别,但是由于逃逸分析也會(huì)有過多的消耗,和前面提到的基于蹤跡的熱點(diǎn)代碼探測(cè)的手段一樣县钥,可能會(huì)獲得的利益小于自身的消耗秀姐,所以虛擬機(jī)也去權(quán)衡是否需要完全準(zhǔn)確的逃逸分析。

四若贮、總結(jié)

主體內(nèi)容還是來自《深入理解Java虛擬機(jī)》省有,也穿插了書中沒有細(xì)講的一些內(nèi)容,這篇博客主要內(nèi)容雖然是總結(jié)的JVM運(yùn)行期的優(yōu)化兜看,但其實(shí)基本上都是在描述JIT編譯器在背后所做的工作锥咸,了解這些能對(duì)Java為何會(huì)如此高效有一個(gè)大體的認(rèn)識(shí),也能增加對(duì)寫出的代碼更加深層次的思考细移,這篇博客就先到這里了搏予。
參考:
https://blog.csdn.net/Luoshengyang/article/details/18006645
http://www.shcas.net/jsjyup/pdf/2017/3/%E5%9F%BA%E4%BA%8ETrace%E7%9A%84CMinus%E8%AF%AD%E8%A8%80%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E6%8A%80%E6%9C%AF.pdf

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弧轧,隨后出現(xiàn)的幾起案子雪侥,更是在濱河造成了極大的恐慌碗殷,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件速缨,死亡現(xiàn)場(chǎng)離奇詭異锌妻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旬牲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門仿粹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人原茅,你說我怎么就攤上這事吭历。” “怎么了擂橘?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵晌区,是天一觀的道長。 經(jīng)常有香客問我通贞,道長朗若,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任昌罩,我火速辦了婚禮哭懈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峡迷。我一直安慰自己银伟,他們只是感情好你虹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布绘搞。 她就那樣靜靜地躺著,像睡著了一般傅物。 火紅的嫁衣襯著肌膚如雪夯辖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天董饰,我揣著相機(jī)與錄音蒿褂,去河邊找鬼。 笑死卒暂,一個(gè)胖子當(dāng)著我的面吹牛啄栓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播也祠,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼昙楚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了诈嘿?” 一聲冷哼從身側(cè)響起堪旧,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤削葱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淳梦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體析砸,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年爆袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了首繁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陨囊,死狀恐怖蛮瞄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谆扎,我是刑警寧澤挂捅,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站堂湖,受9級(jí)特大地震影響闲先,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜无蜂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一伺糠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斥季,春花似錦训桶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躁锡,卻和暖如春午绳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背映之。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工拦焚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杠输。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓赎败,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蠢甲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子僵刮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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