《深入理解Java虛擬機》學習筆記(八)(晚期(運行期)優(yōu)化(JIT編譯器))

晚期(運行期)優(yōu)化

  • 熱點代碼(Hot Spot Code):運行得特別頻繁的某個方法或代碼塊
    • 被多次調(diào)用的方法敌蜂。
    • 被多次執(zhí)行的循環(huán)體赤炒。
  • 即時編譯器(Just In Time Compiler,簡稱JIT編譯器):為了提高熱點代碼的效率押桃,在運行時胎撤,把這些代碼編譯成與本地平臺相關(guān)的機器碼平酿,并進行各種層次的優(yōu)化的編譯器

HotSpot虛擬機內(nèi)的即時編譯器

解釋器與編譯器

當程序需要迅速啟動和執(zhí)行的時候,解釋器可以首先發(fā)揮作用受葛,省去編譯的時間题涨,立即執(zhí)行。在程序運行后总滩,隨著時間的推移纲堵,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后闰渔,可以獲取更高的執(zhí)行效率席函。

圖1 解釋器和編譯器的交互

Client Compiler(C1編譯器)
Server Compiler(C2編譯器(也叫Opto編譯器))

HotSpot虛擬機會逐漸啟用分層編譯(Tiered Compilation)的策略
第0層,程序解釋執(zhí)行冈涧,解釋器不開啟性能監(jiān)控功能(Profiling)茂附,可觸發(fā)第1層編譯。
第1層炕舵,也稱為C1編譯何之,將字節(jié)碼編譯為本地代碼,進行簡單咽筋、 可靠的優(yōu)化溶推,如有必要將加入性能監(jiān)控的邏輯。
第2層(或2層以上)奸攻,也稱為C2編譯蒜危,也是將字節(jié)碼編譯為本地代碼,但是會啟用一些編譯耗時較長的優(yōu)化睹耐,甚至會根據(jù)性能監(jiān)控信息進行一些不可靠的激進優(yōu)化辐赞。

實施分層編譯后,Client Compiler和Server Compiler將會同時工作硝训,許多代碼都可能會被多次編譯响委,用Client Compiler獲取更高的編譯速度,用Server Compiler來獲取更好的編譯質(zhì)量窖梁,在解釋執(zhí)行的時候也無須再承擔收集性能監(jiān)控信息的任務(wù)赘风。

編譯對象與觸發(fā)條件

  • 熱點代碼

    • 被多次調(diào)用的方法。
      • 編譯器理所當然地會以整個方法作為編譯對象纵刘,這種編譯也是虛擬機中標準的JIT編譯方式邀窃。
    • 被多次執(zhí)行的循環(huán)體。
      • 編譯器依然會以整個方法(而不是單獨的循環(huán)體)作為編譯對象假哎。 這種編譯方式因為編譯發(fā)生在方法執(zhí)行過程之中瞬捕,因此形象地稱之為棧上替換(On Stack Replacement鞍历,簡稱為OSR編譯,即方法棧幀還在棧上肪虎,方法就被替換了)
  • 熱點探測

    • 判斷一段代碼是不是熱點代碼劣砍,是不是需要觸發(fā)即時編譯的行為
    • 基于采樣的熱點探測(Sample Based Hot Spot Detection)
      • 虛擬機會周期性地檢查各個線程的棧頂,如果發(fā)現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂扇救,那這個方法就是“熱點方法”秆剪。
    • 基于計數(shù)器的熱點探測(Counter Based Hot Spot Detection)
      • 虛擬機會為每個方法(甚至是代碼塊)建立計數(shù)器,統(tǒng)計方法的執(zhí)行次數(shù)爵政,如果執(zhí)行次數(shù)超過一定的閾值就認為它是“熱點方法”仅讽。

HotSpot虛擬機中使用的是第二種——基于計數(shù)器的熱點探測方法,它為每個方法準備了兩類計數(shù)器:方法調(diào)用計數(shù)器(Invocation Counter)和回邊計數(shù)器(Back Edge Counter)钾挟。

  • 方法調(diào)用計數(shù)器
    • 用于統(tǒng)計方法被調(diào)用的次數(shù)洁灵,它的默認閾值在Client模式下是1500次,在Server模式下是10 000次
    • 當一個方法被調(diào)用時掺出,會先檢查該方法是否存在被JIT編譯過的版本徽千,如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行汤锨。 如果不存在已被編譯過的版本双抽,則將此方法的調(diào)用計數(shù)器值加1,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器值之和是否超過方法調(diào)用計數(shù)器的閾值闲礼。 如果已超過閾值牍汹,那么將會向即時編譯器提交一個該方法的代碼編譯請求。


      圖2 方法調(diào)用計數(shù)器觸發(fā)即時編譯
    • 方法調(diào)用計數(shù)器熱度的衰減
      • 當超過一定的時間限度柬泽,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時編譯器編譯慎菲,那這個方法的調(diào)用計數(shù)器就會被減少一半,這段時間就稱為此方法統(tǒng)計的半衰周期(Counter Half Life Time)
  • 回邊計數(shù)器
    • 統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù)
    • 回邊:在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令
    • 建立回邊計數(shù)器統(tǒng)計的目的就是為了觸發(fā)OSR編譯
    • 虛擬機運行在Client模式下锨并,回邊計數(shù)器閾值計算公式為:
      • 方法調(diào)用計數(shù)器閾值(CompileThreshold)×OSR比率(OnStackReplacePercentage)/100露该。其中OnStackReplacePercentage默認值為933,如果都取默認值第煮,那Client模式虛擬機的回
        邊計數(shù)器的閾值為13995解幼。
    • 虛擬機運行在Server模式下,回邊計數(shù)器閾值的計算公式為:
      • 方法調(diào)用計數(shù)器閾值(CompileThreshold)×(OSR比率(OnStackReplacePercentage)-解釋器監(jiān)控比率(InterpreterProfilePercentage)/100包警。其中OnStackReplacePercentage默認值為140撵摆,InterpreterProfilePercentage默認值為33,如果都取默認值揽趾,那Server模式虛擬機回邊計數(shù)器的閾值為10700台汇。
    • 當解釋器遇到一條回邊指令時苛骨,會先查找將要執(zhí)行的代碼片段是否有已經(jīng)編譯好的版本篱瞎,如果有苟呐,它將會優(yōu)先執(zhí)行已編譯的代碼,否則就把回邊計數(shù)器的值加1俐筋,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器值之和是否超過回邊計數(shù)器的閾值牵素。 當超過閾值的時候,將會提交一個OSR編譯請求澄者,并且把回邊計數(shù)器的值降低一些笆呆,以便繼續(xù)在解釋器中執(zhí)行循環(huán),等待
      編譯器輸出編譯結(jié)果


      圖3 回邊計數(shù)器觸發(fā)即時編譯
    • 與方法計數(shù)器不同粱挡,回邊計數(shù)器沒有計數(shù)熱度衰減的過程赠幕,因此這個計數(shù)器統(tǒng)計的就是該方法循環(huán)執(zhí)行的絕對次數(shù)。 當計數(shù)器溢出的時候询筏,它還會把方法計數(shù)器的值也調(diào)整到溢出狀態(tài)榕堰,這樣下次再進入該方法的時候就會執(zhí)行標準編譯過程。
  • 在確定虛擬機運行參數(shù)的前提下嫌套,這兩個計數(shù)器都有一個確定的閾值逆屡,當計數(shù)器超過閾值溢出了,就會觸發(fā)JIT編譯踱讨。

編譯過程

無論是方法調(diào)用產(chǎn)生的即時編譯請求魏蔗,還是OSR編譯請求,虛擬機在代碼編譯器還未完成之前痹筛,都仍然將按照解釋方式繼續(xù)執(zhí)行莺治,而編譯動作則在后臺的編譯線程中進行。
對于Client Compiler來說帚稠,它是一個簡單快速的三段式編譯器产雹,主要的關(guān)注點在于局部性的優(yōu)化,而放棄了許多耗時較長的全局優(yōu)化手段翁锡。

  • 在第一個階段蔓挖,一個平臺獨立的前端將字節(jié)碼構(gòu)造成一種高級中間代碼表示(HighLevel Intermediate Representaion,HIR)。 HIR使用靜態(tài)單分配(Static Single Assignment,SSA)的形式來代表代碼值馆衔,這可以使得一些在HIR的構(gòu)造過程之中和之后進行的優(yōu)化動作更容易實現(xiàn)瘟判。 在此之前編譯器會在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化,如方法內(nèi)聯(lián)角溃、 常量傳播等優(yōu)化將會在字節(jié)碼被構(gòu)造成HIR之前完成拷获。
  • 在第二個階段,一個平臺相關(guān)的后端從HIR中產(chǎn)生低級中間代碼表示(Low-Level Intermediate Representation,LIR)减细,而在此之前會在HIR上完成另外一些優(yōu)化匆瓜,如空值檢查消除、 范圍檢查消除等,以便讓HIR達到更高效的代碼表示形式驮吱。
  • 最后階段是在平臺相關(guān)的后端使用線性掃描算法(Linear Scan Register Allocation)在LIR上分配寄存器茧妒,并在LIR上做窺孔(Peephole)優(yōu)化,然后產(chǎn)生機器代碼左冬。


    圖4 Client Compiler架構(gòu)

Server Compiler則是專門面向服務(wù)端的典型應(yīng)用并為服務(wù)端的性能配置特別調(diào)整過的編譯器桐筏,也是一個充分優(yōu)化過的高級編譯器,幾乎能達到GNU C++編譯器使用-O2參數(shù)時的優(yōu)化強度拇砰。另外梅忌,還可能根據(jù)解釋器或Client Compiler提供的性能監(jiān)控信息,進行一些不穩(wěn)定的激進優(yōu)化除破,如守護內(nèi)聯(lián)(Guarded Inlining)牧氮、 分支頻率預(yù)測(Branch Frequency Prediction)等。

  • Server Compiler的寄存器分配器是一個全局圖著色分配器瑰枫,它可以充分利用某些處理器架構(gòu)(如RISC)上的大寄存器集合蹋笼。

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

虛擬機設(shè)計團隊幾乎把對代碼的所有優(yōu)化措施都集中在了即時編譯器之中

公共子表達式消除

如果一個表達式E已經(jīng)計算過了,并且從先前的計算到現(xiàn)在E中所有變量的值都沒有發(fā)生變化躁垛,那么E的這次出現(xiàn)就成為了公共子表達式剖毯。 對于這種表達式,沒有必要花時間再對它進行計算教馆,只需要直接用前面計算過的表達式結(jié)果代替E就可以了逊谋。

數(shù)組邊界檢查消除

隱式異常處理

方法內(nèi)聯(lián)

消除方法調(diào)用的成本之外,并其他優(yōu)化手段建立良好的基礎(chǔ)
“類型繼承關(guān)系分析”(Class Hierarchy Analysis,CHA):
是一種基于整個應(yīng)用程序的類型分析技術(shù)土铺,它用于確定在目前已加載的類中胶滋,某個接口是否有多于一種的實現(xiàn),某個類是否存在子類悲敷、 子類是否為抽象類等信息究恤。

  • 編譯器在進行內(nèi)聯(lián)時,如果是非虛方法,直接進行內(nèi)聯(lián)
  • 如果遇到虛方法,則會向CHA查詢此方法在當前程序下是否有多個目標
    版本可供選擇叛复。
    • 如果查詢結(jié)果只有一個版本胰挑,那也可以進行內(nèi)聯(lián)在抛,不過這種內(nèi)聯(lián)就屬于激進優(yōu)化,需要預(yù)留一個“逃生門”(Guard條件不成立時的Slow Path),稱為守護內(nèi)聯(lián)(Guarded Inlining)。 如果程序的后續(xù)執(zhí)行過程中雾叭,虛擬機一直沒有加載到會令這個方法的接收者的繼承關(guān)系發(fā)生變化的類,那這個內(nèi)聯(lián)優(yōu)化的代碼就可以一直使用下去落蝙。 但如果加載了導致繼承關(guān)系發(fā)生變化的新類织狐,那就需要拋棄已經(jīng)編譯的代碼暂幼,退回到解釋狀態(tài)執(zhí)行,或者重新進行編譯移迫。
    • 如果向CHA查詢出來的結(jié)果是有多個版本的目標方法可供選擇旺嬉,則編譯器還將會進行最后一次努力,使用內(nèi)聯(lián)緩存(Inline Cache)來完成方法內(nèi)聯(lián)起意,這是一個建立在目標方法正常入口之前的緩存,它的工作原理大致是:在未發(fā)生方法調(diào)用之前病瞳,內(nèi)聯(lián)緩存狀態(tài)為空揽咕,當?shù)谝淮握{(diào)用發(fā)生后,緩存記錄下方法接收者的版本信息套菜,并且每次進行方法調(diào)用時都比較接收
      者版本亲善,如果以后進來的每次調(diào)用的方法接收者版本都是一樣的,那這個內(nèi)聯(lián)還可以一直用下去逗柴。 如果發(fā)生了方法接收者不一致的情況蛹头,就說明程序真正使用了虛方法的多態(tài)特性,這時才會取消內(nèi)聯(lián)戏溺,查找虛方法表進行方法分派渣蜗。

逃逸分析

分析對象動態(tài)作用域:當一個對象在方法中被定義后,它可能被外部方法所引用旷祸,例如作為調(diào)用參數(shù)傳遞到其他方法中耕拷,稱為方法逃逸。 甚至還有可能被外部線程訪問到托享,譬如賦值給類變量或可以在其他線程中訪問的實例變量骚烧,稱為線程逃逸。

  • 棧上分配(Stack Allocation):如果確定一個對象不會逃逸出方法之外闰围,那讓這個對象在棧上分配內(nèi)存將會是一個很不錯的主意赃绊。由于HotSpot虛擬機目前的實現(xiàn)方式導致棧上分配實現(xiàn)起來比較復雜,因此在HotSpot中暫時還沒有做這項優(yōu)化羡榴。
  • 同步消除(Synchronization Elimination):線程同步本身是一個相對耗時的過程碧查,如果逃逸分析能夠確定一個變量不會逃逸出線程,無法被其他線程訪問校仑,那這個變量的讀寫肯定就不會有競爭么夫,對這個變量實施的同步措施也就可以消除掉。
  • 標量替換(Scalar Replacement):標量(Scalar)是指一個數(shù)據(jù)已經(jīng)無法再分解成更小的數(shù)據(jù)來表示了肤视,Java虛擬機中的原始數(shù)據(jù)類型(int档痪、 long等數(shù)值類型以及reference類型等)都不能再進一步分解,它們就可以稱為標量邢滑。 相對的腐螟,如果一個數(shù)據(jù)可以繼續(xù)分解愿汰,那它就稱作聚合量(Aggregate),Java中的對象就是最典型的聚合量乐纸。 如果把一個Java對象拆散衬廷,根據(jù)程序訪問的情況,將其使用到的成員變量恢復原始類型來訪問就叫做標量替換汽绢。 如果逃逸分析證明一個對象不會被外部訪問吗跋,并且這個對象可以被拆散的話,那程序真正執(zhí)行的時候?qū)⒖赡懿粍?chuàng)建這個對象宁昭,而改為直接創(chuàng)建它的若干個被這個方法使用到的成員變量來代替跌宛。

Java與C/C++的編譯器對比

劣勢:

  • 第一,因為即時編譯器運行占用的是用戶程序的運行時間积仗,具有很大的時間壓力疆拘,它能提供的優(yōu)化手段也嚴重受制于編譯成本。
  • 第二寂曹,Java語言是動態(tài)的類型安全語言哎迄,這就意味著需要由虛擬機來確保程序不會違反語言語義或訪問非結(jié)構(gòu)化內(nèi)存。
  • 第三隆圆,Java語言中雖然沒有virtual關(guān)鍵字漱挚,但是使用虛方法的頻率卻遠遠大于C/C++語言,這意味著運行時對方法接收者進行多態(tài)選擇的頻率要遠遠大于C/C++語言渺氧,也意味著即時編譯器在進行一些優(yōu)化(如前面提到的方法內(nèi)聯(lián))時的難度要遠大于C/C++的靜態(tài)優(yōu)化編譯器棱烂。
  • 第四,Java語言是可以動態(tài)擴展的語言阶女,運行時加載新的類可能改變程序類型的繼承關(guān)系颊糜,這使得很多全局的優(yōu)化都難以進行。
  • 第五秃踩,Java語言中對象的內(nèi)存分配都是堆上進行的衬鱼,只有方法中的局部變量才能在棧上分配。

優(yōu)勢 :

  • 在C/C++中憔杨,別名分析(Alias Analysis)的難度就要遠高于Java鸟赫。
  • Java編譯器另外一個紅利是由它的動態(tài)性所帶來的,由于C/C++編譯器所有優(yōu)化都在編譯期完成消别,以運行期性能監(jiān)控為基礎(chǔ)的優(yōu)化措施它都無法進行抛蚤,如調(diào)用頻率預(yù)測(Call Frequency Prediction)、 分支頻率預(yù)測(Branch Frequency Prediction)寻狂、 裁剪未被選擇的分支(Untaken Branch Pruning)等岁经,這些都會成為Java語言獨有的性能優(yōu)勢。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛇券,一起剝皮案震驚了整個濱河市缀壤,隨后出現(xiàn)的幾起案子樊拓,更是在濱河造成了極大的恐慌,老刑警劉巖塘慕,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筋夏,死亡現(xiàn)場離奇詭異,居然都是意外死亡图呢,警方通過查閱死者的電腦和手機条篷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛤织,“玉大人赴叹,你說我怎么就攤上這事⊥ぃ” “怎么了稚瘾?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵牡昆,是天一觀的道長姚炕。 經(jīng)常有香客問我,道長丢烘,這世上最難降的妖魔是什么柱宦? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮播瞳,結(jié)果婚禮上掸刊,老公的妹妹穿的比我還像新娘。我一直安慰自己赢乓,他們只是感情好忧侧,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牌芋,像睡著了一般蚓炬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躺屁,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天肯夏,我揣著相機與錄音,去河邊找鬼犀暑。 笑死驯击,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的耐亏。 我是一名探鬼主播徊都,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼广辰!你這毒婦竟也來了碟贾?” 一聲冷哼從身側(cè)響起币喧,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袱耽,沒想到半個月后杀餐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡朱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年史翘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀续。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡琼讽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洪唐,到底是詐尸還是另有隱情钻蹬,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布凭需,位于F島的核電站问欠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粒蜈。R本人自食惡果不足惜顺献,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枯怖。 院中可真熱鬧注整,春花似錦、人聲如沸度硝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕊程。三九已至椒袍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間存捺,已是汗流浹背槐沼。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捌治,地道東北人岗钩。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像肖油,于是被迫代替她去往敵國和親兼吓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

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