4. 執(zhí)行引擎

前言

前面介紹過(guò)

JVM被分為三個(gè)主要的子系統(tǒng):

  1. 類加載器子系統(tǒng)
  2. 運(yùn)行時(shí)數(shù)據(jù)區(qū)(也就是內(nèi)存相關(guān))
  3. 執(zhí)行引擎
JVM

前幾章我們簡(jiǎn)單的梳理了一下JVM的類加載機(jī)制及運(yùn)行時(shí)數(shù)據(jù)區(qū),

今天我們來(lái)聊聊JVM執(zhí)行引擎.

如無(wú)特殊說(shuō)明, 所有描述JVM的特性均特指HotSpot VM

Java程序的執(zhí)行過(guò)程

簡(jiǎn)單回顧下Java程序的執(zhí)行過(guò)程

  • 編譯 (Java前端編譯器)
    將Java文件編譯為.class字節(jié)碼文件, 這部分工作由Java前端編譯器完成, 與JVM本身其實(shí)沒(méi)什么關(guān)系;
    編譯過(guò)程如下圖

    編譯

  • 加載(JVM-類加載器)
    負(fù)責(zé)"裝載字節(jié)碼", 但字節(jié)碼并不能夠直接運(yùn)行在操作系統(tǒng)之上, 因?yàn)樽止?jié)碼指令并非等價(jià)于本地機(jī)器指令, 它內(nèi)部包含的僅僅只是一些能夠被JVM鎖識(shí)別的字節(jié)碼指令、符號(hào)表和其他輔助信息.

  • 運(yùn)行(JVM-執(zhí)行引擎)
    Java字節(jié)碼的執(zhí)行是由JVM執(zhí)行引擎來(lái)完成

執(zhí)行引擎

執(zhí)行引擎的任務(wù)就是將字節(jié)碼指令解釋/編譯為對(duì)應(yīng)平臺(tái)上的本地機(jī)器指令.

簡(jiǎn)單來(lái)說(shuō),JVM中的執(zhí)行引擎充當(dāng)了將高級(jí)語(yǔ)言翻譯為機(jī)器語(yǔ)言的翻譯官.

執(zhí)行引擎

科普(翻譯官)

正式介紹執(zhí)行引擎前, 我們先了解下現(xiàn)實(shí)世界的翻譯官這個(gè)職業(yè).

看過(guò)一些跨國(guó)演講節(jié)目的都知道, 一般都會(huì)配多個(gè)翻譯官,來(lái)實(shí)現(xiàn)語(yǔ)言互通.

細(xì)心的朋友可能會(huì)發(fā)現(xiàn), 翻譯的形式一般有兩種

  • 交替?zhèn)髯g
    領(lǐng)導(dǎo)講一句, 翻譯官翻譯一句, 領(lǐng)導(dǎo)繼續(xù)講, 如此往復(fù)

  • 同聲傳譯
    領(lǐng)導(dǎo)講的同時(shí), 翻譯官也同時(shí)翻譯, 基本上領(lǐng)導(dǎo)講完, 翻譯也完成了

不難看出, 同聲傳譯是實(shí)效性比較高的方式, 同時(shí)對(duì)翻譯官的能力、壓力也是極大的.

而交替?zhèn)髯g則顯得從容了很多, 不過(guò)實(shí)效性就差了很多.

那么, 有沒(méi)有辦法可以讓同聲傳譯的門(mén)檻降低點(diǎn)呢? 讓普通點(diǎn)的翻譯官都能勝任這樣的工作呢?!

聰明的小伙伴肯定想到了:

如果能夠提前知道大佬演講的內(nèi)容, 提前翻譯好個(gè)大概, 不就完美解決這個(gè)問(wèn)題了么?!

理想情況下, 是外國(guó)人照著稿子讀, 翻譯官按著提前準(zhǔn)備好的翻譯稿讀出來(lái).

實(shí)際情況下, 這種照本宣科的場(chǎng)景還是比較少的, 大部分情況下是演講者會(huì)有個(gè)大綱, 翻譯官也會(huì)提前拿到, 然后真正演講時(shí)外國(guó)人順著大綱穿插自由發(fā)揮, 這樣, 翻譯官需要注意的其實(shí)就是自由發(fā)揮的那部分內(nèi)容, 這將大大的減輕了翻譯的壓力.

PS: 更真實(shí)通用的情況是:

翻譯官憑借自身的老練和職場(chǎng)經(jīng)驗(yàn), 能夠熟悉的掌握一些話術(shù)的翻譯, 這些話術(shù)來(lái)自于多年的工作經(jīng)驗(yàn).

比如正式的演講“尊敬的各位.....”, 這些其實(shí)是通用的標(biāo)準(zhǔn)的東西, 翻譯官完全可以提前準(zhǔn)備好;

或者, 翻譯官和演講者配合多年, 很有默契, 完全掌握演講者的演講習(xí)慣、話術(shù), 這種默契將有助于翻譯官提前預(yù)知演講者接下來(lái)的發(fā)言.

言歸正傳, 我們開(kāi)始了解執(zhí)行引擎這個(gè)“字節(jié)碼翻譯官”的翻譯方式.

  • 字節(jié)碼解釋器 (交替?zhèn)髯g)

當(dāng)Java虛擬機(jī)啟動(dòng)時(shí)會(huì)根據(jù)預(yù)定義的規(guī)范對(duì)字節(jié)碼采用逐行編譯的方式執(zhí)行,將每條字節(jié)碼文件中的內(nèi)容“翻譯”為對(duì)應(yīng)平臺(tái)的本地機(jī)器指令并執(zhí)行.

  • JIT編譯器 (同聲傳譯)

JVM將源碼直接編譯為和本地機(jī)器平臺(tái)相關(guān)的機(jī)器語(yǔ)言,但是并不會(huì)立刻執(zhí)行.
JIT編譯器在運(yùn)行時(shí)會(huì)針對(duì)那些頻繁被調(diào)用的“熱點(diǎn)代碼”做出深度優(yōu)化,將其直接編譯為對(duì)應(yīng)平臺(tái)的本地機(jī)器指令,以此提升Java程序的執(zhí)行性能.

即時(shí)編譯的目的是避免函數(shù)被解釋執(zhí)行, 而是將整個(gè)函數(shù)體編譯成為機(jī)器碼,每次函數(shù)執(zhí)行時(shí),只執(zhí)行編譯后的機(jī)器碼即可,這種方式可以使執(zhí)行效率大幅度提升.

簡(jiǎn)單的看完以上兩種方式, 不難看出, 其實(shí)JVM執(zhí)行引擎這個(gè)“翻譯官”的翻譯方式和現(xiàn)實(shí)中的翻譯官?zèng)]什么區(qū)別.

早先, JVM執(zhí)行引擎其實(shí)只有“字節(jié)碼解釋器 ”這一種方式, 效率低下, 和C、C++這些語(yǔ)言的執(zhí)行效率簡(jiǎn)直無(wú)法相比;

直到后來(lái)引入了JIT編譯器, 一方面將一些函數(shù)、固定的代碼提前編譯好;
另一方面針對(duì)一些不固定的則繼續(xù)保持解釋執(zhí)行的方式; JVM把兩者搭配使用, 極大的提升了整體的效率.

熱點(diǎn)代碼及探測(cè)方式

JVM執(zhí)行引擎是采用了解釋執(zhí)行+編譯執(zhí)行的混合方式.

PS: 這也就是我們傻傻分不清楚JAVA究竟是解釋型語(yǔ)言還是編譯型語(yǔ)言的原因.

同時(shí)我們也提到了執(zhí)行引擎中的JIT即時(shí)編譯, 關(guān)于JIT即時(shí)編譯有個(gè)很重要的概念, 即熱點(diǎn)代碼.

JIT只會(huì)對(duì)熱點(diǎn)代碼進(jìn)行編譯

熱點(diǎn)代碼

一個(gè)被多次調(diào)用的方法,或者是一個(gè)方法體內(nèi)部循環(huán)次數(shù)較多的循環(huán)體都可以被稱之為“熱點(diǎn)代碼”.
因此都可以通過(guò)JIT編譯器編譯為本地機(jī)器指令.
由于這種編譯方式發(fā)生在方法的執(zhí)行過(guò)程中,因此也被稱之為棧上替換,或簡(jiǎn)稱為OSR (On StackReplacement)編譯.

PS: 熱點(diǎn)代碼經(jīng)過(guò)JIT即時(shí)編譯后成為機(jī)器指令,需要緩存起來(lái)Code Cache,存放在方法區(qū)(元空間/本地內(nèi)存)

一個(gè)方法究竟要被調(diào)用多少次,或者一個(gè)循環(huán)體究竟需要執(zhí)行多少次循環(huán)才可以達(dá)到這個(gè)標(biāo)準(zhǔn)?

必然需要一個(gè)明確的閾值,JIT編譯器才會(huì)將這些“熱點(diǎn)代碼”編譯為本地機(jī)器指令執(zhí)行.

這里主要依靠熱點(diǎn)探測(cè)功能.

熱點(diǎn)探測(cè)

目前HotSpot VM所采用的熱點(diǎn)探測(cè)方式是基于計(jì)數(shù)器的熱點(diǎn)探測(cè).
采用基于計(jì)數(shù)器的熱點(diǎn)探測(cè),HotSpot VM將會(huì)為每一個(gè) 方法都建立2個(gè)不同類型的計(jì)數(shù)器,分別為

  • 方法調(diào)用計(jì)數(shù)器(Invocation Counter)
  • 回邊計(jì)數(shù)器(BackEdge Counter).

方法調(diào)用計(jì)數(shù)器用于統(tǒng)計(jì)方法的調(diào)用次數(shù), 回邊計(jì)數(shù)器則用于統(tǒng)計(jì)循環(huán)體執(zhí)行的循環(huán)次數(shù).

  • 方法調(diào)用計(jì)數(shù)器

這個(gè)計(jì)數(shù)器就用于統(tǒng)計(jì)方法被調(diào)用的次數(shù),它的默認(rèn)閾值在Client 模式下是1500 次, 在Server 模式下是10000 次.
超過(guò)這個(gè)閾值,就會(huì)觸發(fā)JIT編譯.
這個(gè)閾值可以通過(guò)虛擬機(jī)參數(shù)來(lái)人為設(shè)定.

-XX :CompileThreshold

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

方法調(diào)用計(jì)數(shù)器
  • 回邊計(jì)數(shù)器

它的作用是統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊” (Back Edge).
顯然,建立回邊計(jì)數(shù)器統(tǒng)計(jì)的目的就是為了觸發(fā)OSR編譯.

回邊計(jì)數(shù)器

熱度衰減

如果不做任何設(shè)置,方法調(diào)用計(jì)數(shù)器統(tǒng)計(jì)的并不是方法被調(diào)用的絕對(duì)次數(shù),而是一個(gè)相對(duì)的執(zhí)行頻率,即一段時(shí)間之內(nèi)方法被調(diào)用的次數(shù).

當(dāng)超過(guò)一定的時(shí)間限度,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時(shí)編譯器編譯,那這個(gè)方法的調(diào)用計(jì)數(shù)器就會(huì)被減少一半,這個(gè)過(guò)程稱為方法調(diào)用計(jì)數(shù)器熱度的衰減(Counter Decay),而這段時(shí)間就稱為此方法統(tǒng)計(jì)的半衰周期(Counter Half Life Time);

進(jìn)行熱度衰減的動(dòng)作是在虛擬機(jī)進(jìn)行垃圾收集時(shí)順便進(jìn)行的,
可以使用虛擬機(jī)參數(shù) -XX:-UseCounterDecay來(lái)關(guān)閉熱度衰減,讓方法計(jì)數(shù)器統(tǒng)計(jì)方法調(diào)用的絕對(duì)次數(shù);這樣,只要系統(tǒng)運(yùn)行時(shí)間足夠長(zhǎng),絕大部分方法都會(huì)被編譯成本地代碼.
另外,可以使用-XX:CounterHalfLifeTime參數(shù)設(shè)置半衰周期的時(shí)間,單位是秒.

其他熱點(diǎn)探測(cè)技術(shù)

HotSpot中的熱點(diǎn)代碼探測(cè)是基于計(jì)數(shù)器模式實(shí)現(xiàn)的, 但是除了計(jì)數(shù)器的方式探測(cè)之外, 還可以基于采樣(sampling)以及蹤跡(Trace)模式對(duì)代碼進(jìn)行熱點(diǎn)探測(cè).

采樣探測(cè)

采用這種探測(cè)技術(shù)的虛擬機(jī)會(huì)周期性的檢查每個(gè)線程的虛擬機(jī)棧棧頂,如果一些在檢查時(shí)經(jīng)常出現(xiàn)在棧頂?shù)姆椒?那么就代表這個(gè)方法經(jīng)常被調(diào)用執(zhí)行,對(duì)于這類方法可以判定為熱點(diǎn)方法.

  • 優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,可以很輕松的判定出熱度很高(調(diào)用次數(shù)頻繁)的方法
  • 缺點(diǎn):無(wú)法實(shí)現(xiàn)精準(zhǔn)探測(cè),因?yàn)闄z查是周期性的,并且有些方法中存在線程阻塞、休眠等因素,會(huì)導(dǎo)致有些方法無(wú)法被精準(zhǔn)檢測(cè)
蹤跡探測(cè)

采用這種方式的虛擬機(jī)是將一段頻繁執(zhí)行的代碼作為一個(gè)編譯單元,并僅對(duì)該單元進(jìn)行編譯,該單元由一個(gè)線性且連續(xù)的指令集組成,僅有一個(gè)入口,但有多個(gè)出口.
也就代表著:基于蹤跡而編譯的熱點(diǎn)代碼不僅僅局限在一個(gè)單獨(dú)的方法或者代碼塊中,一條蹤跡可能對(duì)應(yīng)多個(gè)方法,代碼中頻繁執(zhí)行的路徑就可能被識(shí)別成不同的蹤跡.

  • 優(yōu)點(diǎn):這種方式實(shí)現(xiàn)可以使得熱點(diǎn)探測(cè)擁有更高精度,可以避免將一塊代碼塊中所有的代碼都進(jìn)行編譯的情況出現(xiàn),能夠在很大程序上減少不必要的編譯開(kāi)銷.
    因?yàn)闊o(wú)論是采樣探測(cè)還是計(jì)數(shù)器探測(cè)的方式,都是以方法體或循環(huán)體作為編譯的基本單元的.
  • 缺點(diǎn):蹤跡探測(cè)的實(shí)現(xiàn)過(guò)程非常復(fù)雜,難度非常高.

HotSpot虛擬機(jī)采用的計(jì)數(shù)探測(cè)的方式,實(shí)現(xiàn)難度、編譯開(kāi)銷與探測(cè)精準(zhǔn)三者之間會(huì)有一個(gè)很好的權(quán)衡.

三種探測(cè)技術(shù)比較如下:

  • 實(shí)現(xiàn)難度:采樣探測(cè) < 計(jì)數(shù)探測(cè) < 蹤跡探測(cè)
  • 探測(cè)精度:采樣探測(cè) < 計(jì)數(shù)探測(cè) < 蹤跡探測(cè)
  • 編譯開(kāi)銷:蹤跡探測(cè) < 計(jì)數(shù)探測(cè) < 采樣探測(cè)

JVM為何不移除解釋器?

很多同學(xué)可能會(huì)有疑惑, 如果以純JIT編譯器的方式執(zhí)行,性能方面絕對(duì)會(huì)超出解釋器+編譯器混合的模式,但為何虛擬機(jī)至今也不移除解釋器,還要用解釋器來(lái)拖累Java程序的性能呢?

其實(shí)主要有兩個(gè)原因:

  • 保證Java的絕對(duì)跨平臺(tái)性

如果將解釋器從虛擬機(jī)中移除就代表著:
每到一個(gè)不同的平臺(tái),比如從Windows遷移到Linux環(huán)境,那么JIT又要重新編譯生成對(duì)應(yīng)平臺(tái)的機(jī)器碼指令才能讓Java程序執(zhí)行

  • 保證啟動(dòng)速度

如果移除了解釋器模塊,那么就代表著所有的字節(jié)碼指令需要在啟動(dòng)時(shí)全部先編譯為本地的機(jī)械碼,這樣才能使得Java程序能夠正常執(zhí)行.
此時(shí)時(shí)間開(kāi)銷是巨大的,那么會(huì)導(dǎo)致一些需要緊急上線的項(xiàng)目可能編譯都需要等半天的時(shí)間

PS: 的確有JVM的實(shí)現(xiàn)移除了解釋器模塊, 就是號(hào)稱“史上最快”的JRockitVM

總結(jié)

HotSpot中采用的是解釋器+JIT即時(shí)編譯器混合.

好處
在Java程序運(yùn)行時(shí),JVM可以快速啟動(dòng),前期先由解釋器發(fā)揮作用,不需要等到編譯器把所有字節(jié)碼指令編譯完之后才執(zhí)行,這樣可以省去很大一部分的編譯時(shí)間;
后續(xù)隨著程序在線上運(yùn)行的時(shí)間越來(lái)越久,JIT發(fā)揮作用,慢慢的將一些程序中的熱點(diǎn)代碼替換為本地機(jī)器碼運(yùn)行,這樣可以讓程序的執(zhí)行效率更高.
同時(shí),因?yàn)镠otSpotVM中存在熱度衰減的概念,所以當(dāng)一段代碼的熱度下降時(shí),JIT會(huì)取消對(duì)它的編譯,重新更換為解釋器執(zhí)行的模式工作, HotSpot的這種執(zhí)行模式也被成為“自適應(yīng)優(yōu)化”執(zhí)行.

當(dāng)然,我們?cè)诔绦騿?dòng)時(shí)也可以通過(guò)JVM參數(shù)自己指定執(zhí)行模式

  • -Xint:完全采用解釋器模式執(zhí)行程序
  • -Xcomp:完全采用即時(shí)編譯器模式執(zhí)行程序. 如果即時(shí)編譯器出現(xiàn)問(wèn)題,解釋器會(huì)介入執(zhí)行
  • -Xmixed:采用解釋器+JIT即時(shí)編譯器的混合模式共同執(zhí)行(默認(rèn)的執(zhí)行方式)

HotSpot VM 中的JIT分類

在HotSpot VM中內(nèi)嵌有兩個(gè)JIT編譯器

  • Client Compiler(簡(jiǎn)稱C1)

  • Server Compiler(簡(jiǎn)稱C2)

C1編譯器(Client Compiler)

C1編譯器主要追求穩(wěn)定和編譯速度,屬于保守派.

C1中常見(jiàn)的優(yōu)化方案有幾種

  • 公共子表達(dá)式消除

如果一個(gè)表達(dá)式E已經(jīng)計(jì)算過(guò)了,并且從先前的計(jì)算到現(xiàn)在E中所有變量的值都沒(méi)有發(fā)生變化,那E的這次出現(xiàn)就成公共子表達(dá)式,可以用原先的表達(dá)式進(jìn)行消除,直接使用上次的計(jì)算結(jié)果,無(wú)需再次計(jì)算

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

將引用的方法代碼編譯到引用點(diǎn)處,這樣可以減少棧幀的生成,減少參數(shù)傳遞以及跳轉(zhuǎn)過(guò)程

  • 去虛擬化

對(duì)唯一的實(shí)現(xiàn)類進(jìn)行內(nèi)聯(lián)

  • 冗余消除

通過(guò)對(duì)字節(jié)碼指令進(jìn)行流分析,將一些運(yùn)行過(guò)程中不會(huì)執(zhí)行的代碼消除

  • 空檢測(cè)消除
    將顯式調(diào)用的NullCheck(空指針判斷)擦除,改成ImplicitNullCheck異常信號(hào)機(jī)制處理
  • 自動(dòng)裝箱消除
    對(duì)于一些不必要的裝箱操作會(huì)被消除,比如剛裝箱的數(shù)據(jù)又在后面立馬被拆箱,這種無(wú)用操作就會(huì)被消除
  • 安全點(diǎn)消除
    對(duì)于線程無(wú)法抵達(dá)或不會(huì)停留的安全點(diǎn)會(huì)進(jìn)行消除
  • 反射消除
    對(duì)于一些可以正常訪問(wèn)無(wú)需通過(guò)反射機(jī)制獲取的數(shù)據(jù),會(huì)被改為直接訪問(wèn),消除反射操作

C2編譯器(Server Compiler)

C2編譯器則主要是追求編譯后的執(zhí)行性能,屬于激進(jìn)派.

C2編譯器建立在C1編譯器的基礎(chǔ)優(yōu)化之上,除了使用C1中的優(yōu)化手段之外,還有幾種基于逃逸分析的激進(jìn)優(yōu)化手段

  • 標(biāo)量替換

用標(biāo)量值代替聚合對(duì)象的屬性值

  • 棧上分配

對(duì)于未逃逸的對(duì)象分配對(duì)象在棧而不是堆

  • 同步消除

清除同步操作,通常指synchronized

逃逸分析

Java 中對(duì)象的創(chuàng)建一般會(huì)由堆內(nèi)存去分配內(nèi)存空間來(lái)進(jìn)行存儲(chǔ),在堆內(nèi)存空間不足的時(shí)候,GC 便會(huì)對(duì)堆內(nèi)存進(jìn)行垃圾回收.

如果 GC 運(yùn)行的次數(shù)過(guò)多,便會(huì)影響程序的性能,所以 “逃逸分析” 由此誕生.

它的目的就是判斷哪些對(duì)象是可以存儲(chǔ)在棧內(nèi)存中而不用存儲(chǔ)在堆內(nèi)存中,從而讓其隨著線程的消逝而消逝,進(jìn)而減少 GC 發(fā)生的頻率.

簡(jiǎn)而言之, 逃逸分析就是Hotspot 虛擬機(jī)可以分析新創(chuàng)建對(duì)象的使用范圍,并決定是否在 Java 堆上分配內(nèi)存的一項(xiàng)技術(shù).

逃逸分析是建立在方法為單位之上的,如果一個(gè)成員對(duì)象在方法體中產(chǎn)生,但是直至方法結(jié)束也沒(méi)有走出方法體的作用域, 那么該成員就可以被理解為未逃逸.

反之,如果一個(gè)成員在方法最后被“return”出去了, 或在方法體的邏輯中被賦值給了外部成員,那么則代表著該成員逃逸了.

逃逸的方式
  • 方法逃逸

一個(gè)對(duì)象在方法中被定義,但卻被方法以外的其他代碼使用.

在一個(gè)方法體內(nèi),定義一個(gè)局部變量,而它可能被外部方法引用.
比如作為調(diào)用參數(shù)傳遞給方法,或作為對(duì)象直接返回
可以理解為對(duì)象跳出了方法

  • 線程逃逸

一個(gè)對(duì)象由某個(gè)線程在方法中被定義,但卻被其他線程訪問(wèn)

這個(gè)對(duì)象被其他線程訪問(wèn)到,比如賦值給了實(shí)例變量,并被其他線程訪問(wèn)到了.
可以理解為對(duì)象逃出了當(dāng)前線程

逃逸的狀態(tài)

一個(gè)對(duì)象有三種逃逸狀態(tài)

  • 全局逃逸

一個(gè)對(duì)象的作用范圍逃出了當(dāng)前方法或者當(dāng)前線程

一般有以下幾種場(chǎng)景

  • 對(duì)象是一個(gè)靜態(tài)變量
  • 對(duì)象是一個(gè)已經(jīng)發(fā)生逃逸的對(duì)象
  • 對(duì)象作為當(dāng)前方法的返回值
  • 參數(shù)逃逸

一個(gè)對(duì)象被作為方法參數(shù)傳遞或者被參數(shù)引用,但在調(diào)用過(guò)程中不會(huì)發(fā)生全局逃逸,這個(gè)狀態(tài)是通過(guò)被調(diào)方法的字節(jié)碼確定的

  • 沒(méi)有逃逸

方法中的對(duì)象沒(méi)有發(fā)生逃逸

逃逸分析的好處

逃逸分析的作用,就是篩選出沒(méi)有發(fā)生逃逸的對(duì)象,從而對(duì)它們進(jìn)行以下三方面的優(yōu)化

  • 同步消除(鎖消除)
    如果你定義的類的方法上有同步鎖,但在運(yùn)行時(shí),卻只有一個(gè)線程在訪問(wèn),此時(shí)逃逸分析后的機(jī)器碼,會(huì)去掉同步鎖運(yùn)行
  • 標(biāo)量替換
    Java虛擬機(jī)中的原始數(shù)據(jù)類型(int,long等數(shù)值類型以及reference類型等)都不能再進(jìn)一步分解,它們可以稱為標(biāo)量.
    相對(duì)的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它稱為聚合量.
    Java中最典型的聚合量是對(duì)象.
    如果逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),并且這個(gè)對(duì)象是可分解的,那程序真正執(zhí)行的時(shí)候?qū)⒖赡懿粍?chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)代替.
    拆散后的變量便可以被單獨(dú)分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對(duì)象就無(wú)需整體分配空間了

  • 棧內(nèi)存分配
    將原本分配在堆內(nèi)存上的對(duì)象轉(zhuǎn)而分配在棧內(nèi)存上,這樣就可以減少堆內(nèi)存的占用,從而減少 GC 的頻次

相關(guān)參數(shù)配置
-XX:+DoEscapeAnalysis   開(kāi)啟逃逸分析(JDK1.8 中是默認(rèn)開(kāi)啟)
-XX:-DoEscapeAnalysis    關(guān)閉逃逸分析
-XX:+PrintEscapeAnalysis  顯示分析結(jié)果

-XX:+EliminateLocks    開(kāi)啟鎖消除(JDK1.8 中是默認(rèn)開(kāi)啟)
-XX:-EliminateLocks    關(guān)閉鎖消除

-XX:+EliminateAllocations    開(kāi)啟標(biāo)量替換(JDK1.8 中是默認(rèn)開(kāi)啟)
-XX:-EliminateAllocations    關(guān)閉標(biāo)量替換
-XX:+PrintEliminateAllocations    顯示標(biāo)量替換詳情
逃逸實(shí)例

下面這段代碼演示了逃逸

public class EscapeAnalysisDemo {

    public static Object globalVariableObject;

    public Object instanceObject;

    public void globalVariableEscape(){
        globalVariableObject = new Object();  // 靜態(tài)變量,外部線程可見(jiàn),發(fā)生逃逸
    }

    public void instanceObjectEscape(){
        instanceObject = new Object();  // 賦值給堆中實(shí)例字段,外部線程可見(jiàn),發(fā)生逃逸
    }
    
    public Object returnObjectEscape(){
        return new Object();   // 返回實(shí)例,外部線程可見(jiàn),發(fā)生逃逸
    }

    public void noEscape(){
        Object noEscape = new Object();   // 僅創(chuàng)建線程可見(jiàn),對(duì)象無(wú)逃逸
    }

}

C1和C2的對(duì)比

  • C2編譯器啟動(dòng)時(shí)長(zhǎng)比C1編譯器慢
  • 系統(tǒng)穩(wěn)定執(zhí)行以后C2編譯器執(zhí)行速度遠(yuǎn)遠(yuǎn)快于C1編譯器

程序解釋執(zhí)行(不開(kāi)啟性能監(jiān)控)可以觸發(fā)C1編譯,將字節(jié)碼編譯成機(jī)器碼,可以進(jìn)行簡(jiǎn)單優(yōu)化,也可以加上性能監(jiān)控;

C2編譯會(huì)根據(jù)性能監(jiān)控信息進(jìn)行激進(jìn)優(yōu)化.

不過(guò)在Java7版本之后,一旦開(kāi)發(fā)人員在程序中顯式指定命令“-server"時(shí),默認(rèn)將會(huì)開(kāi)啟分層編譯策略,由C1編譯器和C2編譯器相互協(xié)作共同來(lái)執(zhí)行編譯任務(wù).

開(kāi)發(fā)人員可以通過(guò)命令顯式指定Java虛擬機(jī)在運(yùn)行時(shí)到底使用哪一種即時(shí)編譯器

-client
指定Java虛擬機(jī)運(yùn)行在Client模式下,并使用C1編譯器;
C1編譯器會(huì)對(duì)字節(jié)碼進(jìn)行簡(jiǎn)單和可靠的優(yōu)化,耗時(shí)短,以達(dá)到更快的編譯速度

-server
指定Java虛擬機(jī)運(yùn)行在Server模式下,并使用C2編譯器.
C2進(jìn)行耗時(shí)較長(zhǎng)的優(yōu)化,以及激進(jìn)優(yōu)化,但優(yōu)化的代碼執(zhí)行效率更高

注意:64位操作系統(tǒng)默認(rèn)使用-server服務(wù)器模式,即C2編譯器

Graal編譯器

JDK10起,HotSpot又加入一個(gè)全新的即時(shí)編譯器: Graal編譯器

編譯效果短短幾年時(shí)間就追平了C2編譯器,未來(lái)可期.

目前,帶著“實(shí)驗(yàn)狀態(tài)"標(biāo)簽,需要使用開(kāi)關(guān)參數(shù)去激活,才可以使用

-XX: +UnlockExperimentalVMOptions 
-XX: +UseJVMCICompiler

AOT編譯器

JDK9引入了AOT編譯器(靜態(tài)提前編譯器,Ahead Of Time Compiler)

Java 9引入了實(shí)驗(yàn)性AOT編譯工具jaotc,它借助了Graal 編譯器,將所輸入的Java 類文件轉(zhuǎn)換為機(jī)器碼,并存放至生成的動(dòng)態(tài)共享庫(kù)之中.

所謂AOT編譯,是與即時(shí)編譯相對(duì)立的一個(gè)概念.

我們知道,即時(shí)編譯指的是在程序的運(yùn)行過(guò)程中,將字節(jié)碼轉(zhuǎn)換為可在硬件上直接運(yùn)行的機(jī)器碼,并部署至托管環(huán)境中的過(guò)程.

而AOT編譯指的則是,在程序運(yùn)行之前,便將字節(jié)碼轉(zhuǎn)換為機(jī)器碼的過(guò)程.

最大好處:
Java虛擬機(jī)加載已經(jīng)預(yù)編譯成二進(jìn)制庫(kù),可以直接執(zhí)行.不必等待即時(shí)編譯器的預(yù)熱,減少Java應(yīng)用給人帶來(lái)“第一次運(yùn)行慢”的不良體驗(yàn)

缺點(diǎn):
破壞了java"一次編譯,到處運(yùn)行”(提前干掉了能夠跨平臺(tái)的class文件),必須為每個(gè)不同硬件、oS編譯對(duì)應(yīng)的發(fā)行包.
降低了Java鏈接過(guò)程的動(dòng)態(tài)性,加載的代碼在編譯期就必須全部已知.
還需要繼續(xù)優(yōu)化中,最初只支持Linux x64 java base

請(qǐng)關(guān)注我的訂閱號(hào)

訂閱號(hào).png

參考

  • 《深入理解JAVA虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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