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

From:深入理解Java虛擬機(jī)

JIT【Just In Time】出現(xiàn)的原因

Java程序最初通過(guò)解釋器進(jìn)行解釋執(zhí)行蹋半,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁時(shí)垮衷,就會(huì)把這些代碼定為【熱點(diǎn)代碼】。為了提高熱點(diǎn)代碼的執(zhí)行效率册招,在運(yùn)行時(shí)抖单,虛擬機(jī)將會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼萎攒,并進(jìn)行各種層次的優(yōu)化,完成這個(gè)任務(wù)的編譯器就成為即時(shí)編譯器矛绘。

JIT并不是虛擬機(jī)的必需部分耍休,但即時(shí)編譯器【性能的好壞、代碼的優(yōu)化程度】卻是衡量一款虛擬機(jī)的關(guān)鍵指標(biāo)之一货矮。

Java虛擬機(jī)規(guī)范中沒(méi)有具體約束和限制即時(shí)編譯器應(yīng)該如何實(shí)現(xiàn)羊精,所以JIT是跟具體虛擬機(jī)的實(shí)現(xiàn)有關(guān)的。本文以HotSpot虛擬機(jī)中的JIT為例說(shuō)明次屠。

解釋器和編譯器的對(duì)比

1)當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用雳刺,省去編譯的時(shí)間劫灶,立即執(zhí)行。在程序運(yùn)行后掖桦,隨著時(shí)間推移本昏,編譯器逐漸發(fā)揮作用,把越來(lái)越多的代碼編譯成本地代碼枪汪,可以獲取更高的執(zhí)行效率涌穆。

2)當(dāng)程序運(yùn)行環(huán)境內(nèi)存資源限制較大怔昨,如:嵌入式系統(tǒng),可以使用解釋執(zhí)行節(jié)約內(nèi)存宿稀;反之使用編譯器執(zhí)行來(lái)提升效率趁舀。

3)解釋器可以作為編譯器激進(jìn)優(yōu)化時(shí)的一個(gè)【逃生門(mén)】,讓編譯器根據(jù)概率選擇一些大多數(shù)時(shí)候都能提升運(yùn)行速度的優(yōu)化手段祝沸,當(dāng)激進(jìn)優(yōu)化的假設(shè)不成立時(shí)矮烹,可以通過(guò)逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行。【即編譯器失敗后罩锐,采用解釋器繼續(xù)執(zhí)行】

HotSpot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器奉狈,C1【Client Compiler】和 C2【Server Compiler】。HotSpot會(huì)根據(jù)自身版本與宿主機(jī)器的硬件性能自動(dòng)選擇一個(gè)即時(shí)編譯器涩惑,并與解釋器配合工作仁期。

為了在程序響應(yīng)速度運(yùn)行效率之間達(dá)到最佳平衡,HotSpot會(huì)逐漸啟用【分層編譯】策略竭恬,其中包括:
第0層:程序解釋執(zhí)行跛蛋,解釋器不開(kāi)啟性能監(jiān)控功能,可觸發(fā)第1層編譯萍聊。
第1層:也稱(chēng)為C1編譯问芬,將字節(jié)碼編譯為本地代碼,進(jìn)行簡(jiǎn)單寿桨、可靠的優(yōu)化此衅,如有必要將加入性能監(jiān)控的邏輯⊥っ【只關(guān)注局部?jī)?yōu)化挡鞍,放棄了耗時(shí)較長(zhǎng)的全局優(yōu)化】
第2層:也稱(chēng)為C2編譯,將字節(jié)碼編譯為本地代碼预烙,開(kāi)啟一些編譯較長(zhǎng)的優(yōu)化墨微,甚至?xí)鶕?jù)性能監(jiān)測(cè)信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化。

分層編譯后扁掸,C1【Client Compiler】和C2【Server Compiler】將同時(shí)工作翘县,許多代碼可能被多次編譯,用C1獲取更高的編譯速度谴分,用C2獲取更好的編譯質(zhì)量锈麸。

JIT的編譯對(duì)象

即時(shí)編譯的【熱點(diǎn)代碼】有兩類(lèi):
1)被多次調(diào)用的方法
2)被多次執(zhí)行的循環(huán)體

這兩種情況都是以整個(gè)方法作為編譯對(duì)象。對(duì)于情況2)而言牺蹄,JIT編譯發(fā)生在方法執(zhí)行過(guò)程之中忘伞,方法棧楨還在棧上,該方法就被替換為JIT編譯的產(chǎn)物,因此形象的稱(chēng)為【棧上替換氓奈,On Stack Replacement翘魄,OSR編譯】。

JIT觸發(fā)的條件

【多次執(zhí)行】中的多次是如何計(jì)算的舀奶,即如何判斷一段代碼是不是熱點(diǎn)代碼暑竟,需不需要即時(shí)編譯,這種行為稱(chēng)為【熱點(diǎn)探測(cè)】伪节。熱點(diǎn)探測(cè)的兩種方式:
1)基于采樣的熱點(diǎn)探測(cè)
虛擬機(jī)周期性的檢查各個(gè)線(xiàn)程的棧頂光羞,當(dāng)發(fā)現(xiàn)某個(gè)方法經(jīng)常出現(xiàn)在棧頂,那這個(gè)方法就是熱點(diǎn)方法怀大。該方法能夠容易的獲取方法調(diào)用關(guān)系【將調(diào)用棧展開(kāi)即可】纱兑。
2)基于計(jì)數(shù)器的熱點(diǎn)探測(cè)【HostSpot采用】
虛擬機(jī)為每個(gè)方法建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù)化借,如果超過(guò)閾值則為熱點(diǎn)方法潜慎。

HotSpot使用第二種基于計(jì)數(shù)器的熱點(diǎn)探測(cè),每個(gè)方法中有兩類(lèi)計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器蓖康。閾值默認(rèn):在Client模式下為1500次铐炫,在Server模式下為10000次。

方法調(diào)用計(jì)數(shù)器統(tǒng)計(jì)的不是方法被調(diào)用的絕對(duì)值蒜焊,因?yàn)楫?dāng)超過(guò)一定的時(shí)間限制倒信,如果方法的調(diào)用次數(shù)還是不能達(dá)到閾值,那這個(gè)方法計(jì)數(shù)器的值就會(huì)減半泳梆,這個(gè)過(guò)程稱(chēng)為【方法調(diào)用計(jì)數(shù)器熱度的衰減】鳖悠。
熱度衰減的動(dòng)作是在垃圾收集時(shí)順便進(jìn)行的∮琶睿可以通過(guò)參數(shù)關(guān)閉熱度衰減乘综,這樣方法調(diào)用計(jì)數(shù)器統(tǒng)計(jì)的就是絕對(duì)值了。

回邊計(jì)數(shù)器:用于統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù)套硼。
在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱(chēng)為【回邊】卡辰。
統(tǒng)計(jì)的是絕對(duì)值,它沒(méi)有熱度衰減過(guò)程邪意。

還有其它熱點(diǎn)探測(cè)方法九妈,如:基于蹤跡的熱點(diǎn)探測(cè),Android中的Dalvik虛擬機(jī)就是使用該種方法雾鬼。

觸發(fā)JIT的過(guò)程

方法調(diào)用計(jì)數(shù)器觸發(fā)即時(shí)編譯

注意:當(dāng)兩個(gè)計(jì)數(shù)器之和超過(guò)閾值時(shí)萌朱,向編譯器提交JIT請(qǐng)求,這個(gè)時(shí)候不會(huì)等待JIT的結(jié)果呆贿,而是繼續(xù)按照解釋器的方式執(zhí)行字節(jié)碼嚷兔,直到JIT完成。當(dāng)JIT完成之后做入,這個(gè)方法的調(diào)用入口地址就被系統(tǒng)自動(dòng)改寫(xiě)成新的冒晰。即虛擬機(jī)在代碼編譯器還沒(méi)有完成之前,都將繼續(xù)按照解釋方式執(zhí)行竟块,而編譯動(dòng)作則在后臺(tái)編譯線(xiàn)程中進(jìn)行壶运。

JVM大部分優(yōu)化都是在JIT過(guò)程中,JIT產(chǎn)生的本地代碼比Javac產(chǎn)生的字節(jié)碼更加優(yōu)秀浪秘。

守護(hù)內(nèi)聯(lián) 內(nèi)聯(lián)緩存【激進(jìn)優(yōu)化】

內(nèi)聯(lián)優(yōu)化的好處:
1)去除方法調(diào)用的成本蒋情,如:建立棧楨。
2)方法內(nèi)聯(lián)后便可以在更大范圍上采取后續(xù)的優(yōu)化手段耸携。

對(duì)于使用invokespecial指令調(diào)用的私有方法棵癣、實(shí)例構(gòu)造器、父類(lèi)方法夺衍,以及使用invokestatic指令進(jìn)行調(diào)用的靜態(tài)方法狈谊,再有被final修飾的方法,他們?cè)诰幾g期間進(jìn)行解析沟沙『尤埃【但生成的class文件沒(méi)有做內(nèi)聯(lián)優(yōu)化】

  • 虛方法的內(nèi)聯(lián)

為了解決虛方法的內(nèi)聯(lián)問(wèn)題,JVM引入了一種【類(lèi)型繼承關(guān)系分析矛紫,Class Hierarchy Analysis赎瞎,CHA】技術(shù),它用于確定在目標(biāo)已加載的類(lèi)中颊咬,某個(gè)接口是否有多于一種的實(shí)現(xiàn)务甥,某個(gè)類(lèi)是否存在子類(lèi)、子類(lèi)是否為抽象類(lèi)等信息贪染。

編譯器在進(jìn)行內(nèi)聯(lián)時(shí)缓呛,如果是非虛方法,則直接進(jìn)行內(nèi)聯(lián)杭隙,這個(gè)時(shí)候內(nèi)聯(lián)是有穩(wěn)定保障的哟绊。如果遇到虛方法,則會(huì)向CHA查詢(xún)此方法在當(dāng)前程序下是否有多個(gè)目標(biāo)版本痰憎。

if :如果只有一個(gè)版本票髓,則也可以進(jìn)行內(nèi)聯(lián),不過(guò)這種內(nèi)聯(lián)屬于激進(jìn)優(yōu)化铣耘,需要預(yù)留一個(gè)【逃生門(mén)】洽沟,稱(chēng)為【守護(hù)內(nèi)聯(lián)】。如果程序在后續(xù)執(zhí)行過(guò)程中蜗细,虛擬機(jī)一直沒(méi)有加載到會(huì)令這個(gè)方法的接收者的繼承關(guān)系發(fā)生變化的類(lèi)裆操,那這個(gè)內(nèi)聯(lián)優(yōu)化的代碼可以一直使用下去怒详。但如果加載了導(dǎo)致繼承關(guān)系發(fā)生變化的類(lèi),那就需要拋棄已經(jīng)編譯的代碼踪区,退回到解釋狀態(tài)執(zhí)行昆烁,或重新進(jìn)行編譯

else :如果有多個(gè)版本的目標(biāo)方法可供選擇缎岗,此時(shí)編譯器使用【內(nèi)聯(lián)緩存】 來(lái)完成方法內(nèi)聯(lián)静尼,這是一個(gè)建立在目標(biāo)方法入口之前的緩存
工作原理:在未發(fā)生方法調(diào)用之前传泊,內(nèi)聯(lián)緩存為空鼠渺;當(dāng)?shù)谝淮握{(diào)用發(fā)生后,內(nèi)聯(lián)緩存記錄下方法接收者的版本信息眷细,并且每次進(jìn)行方法調(diào)用時(shí)都比較接收者的版本拦盹,如果接收者的版本始終保持一致,那這個(gè)內(nèi)聯(lián)可以一直用下去溪椎;如果發(fā)生了方法接收者版本不一致的情況掌敬,說(shuō)明程序真正在使用虛方法的多態(tài)特性,此時(shí)取消內(nèi)聯(lián)池磁,查找【虛方法表】進(jìn)行方法分派奔害。

如此看來(lái),虛擬機(jī)都會(huì)把方法優(yōu)化為內(nèi)聯(lián)方法地熄,只不過(guò)在有些情況下為激進(jìn)優(yōu)化华临,當(dāng)激進(jìn)優(yōu)化被打破時(shí),再改為解釋狀態(tài)執(zhí)行端考,或重新編譯雅潭,或取消內(nèi)聯(lián)。

逃逸分析

逃逸分析的基本行為就是分析對(duì)象動(dòng)態(tài)作用域却特。
方法逃逸:當(dāng)一個(gè)對(duì)象在方法中被定義后扶供,它被外部【方法】所引用,如:作為調(diào)用參數(shù)傳遞出去裂明。
線(xiàn)程逃逸:當(dāng)一個(gè)對(duì)象在方法中被定義后椿浓,它被外部【線(xiàn)程】所訪問(wèn),如:將該局部變量賦值給類(lèi)變量闽晦,或賦值給可以在其他線(xiàn)程中訪問(wèn)的實(shí)例變量扳碍。

對(duì)非逃逸對(duì)象可進(jìn)行的優(yōu)化:
1)棧上分配
當(dāng)一個(gè)對(duì)象不會(huì)逃逸出方法外,可以?xún)?yōu)化將對(duì)象內(nèi)存在棧上進(jìn)行分配仙蛉,這樣對(duì)象可以隨著棧楨出棧而銷(xiāo)毀笋敞,減小垃圾回收系統(tǒng)的壓力≤瘢【HotSpot不支持】
2)同步消除
當(dāng)變量不會(huì)逃逸出線(xiàn)程時(shí)夯巷,無(wú)法被其它線(xiàn)程訪問(wèn)到赛惩,可以將對(duì)這個(gè)變量實(shí)施的同步措施進(jìn)行消除。
3)標(biāo)量替換
標(biāo)量:數(shù)據(jù)無(wú)法再分解成更小的數(shù)據(jù)來(lái)表示趁餐,即Java中的基本數(shù)據(jù)類(lèi)型坊秸。
聚合量:數(shù)據(jù)可以再進(jìn)一步進(jìn)行分解,如:對(duì)象澎怒。
標(biāo)量替換:把一個(gè)Java對(duì)象拆分,將其使用到的成員變量恢復(fù)為原始類(lèi)型來(lái)訪問(wèn)阶牍。

如果一個(gè)對(duì)象不會(huì)逃逸喷面,那程序真正執(zhí)行的時(shí)候可能不創(chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的成員變量走孽。將對(duì)象拆分后惧辈,除了可以讓對(duì)象的成員變量在棧上分配和讀寫(xiě)之外,還可以為后續(xù)進(jìn)一步的優(yōu)化手段創(chuàng)造條件磕瓷。

標(biāo)量替換例子

package com.ljg;

public class T {

  static class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
      this.name = name;
      this.age = age;
    }
  }

  public static void main(String[] args) {
    Person person = new Person("ljg", 18);
    System.out.println(person.name + "age is " + person.age);
  }
}

上面代碼可能優(yōu)化為:【想不到的優(yōu)化】

public static void main(String[] args) {
  String name = "ljg";
  int age = 18;
  System.out.println(name + "age is " + age);
}

這樣就不存在垃圾回收了盒齿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市困食,隨后出現(xiàn)的幾起案子边翁,更是在濱河造成了極大的恐慌,老刑警劉巖硕盹,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件符匾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瘩例,警方通過(guò)查閱死者的電腦和手機(jī)啊胶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)垛贤,“玉大人焰坪,你說(shuō)我怎么就攤上這事∑傅耄” “怎么了某饰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)善绎。 經(jīng)常有香客問(wèn)我露乏,道長(zhǎng),這世上最難降的妖魔是什么涂邀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任瘟仿,我火速辦了婚禮,結(jié)果婚禮上比勉,老公的妹妹穿的比我還像新娘劳较。我一直安慰自己驹止,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布观蜗。 她就那樣靜靜地躺著臊恋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪墓捻。 梳的紋絲不亂的頭發(fā)上抖仅,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音砖第,去河邊找鬼撤卢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梧兼,可吹牛的內(nèi)容都是我干的放吩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼羽杰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼渡紫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起考赛,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惕澎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颜骤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體集灌,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年复哆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欣喧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梯找,死狀恐怖唆阿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锈锤,我是刑警寧澤驯鳖,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站久免,受9級(jí)特大地震影響浅辙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阎姥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一记舆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呼巴,春花似錦泽腮、人聲如沸御蒲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厚满。三九已至,卻和暖如春碧磅,著一層夾襖步出監(jiān)牢的瞬間碘箍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工鲸郊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丰榴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓严望,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逻恐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子像吻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348