<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM JIT動(dòng)態(tài)編譯

          共 3898字,需瀏覽 8分鐘

           ·

          2021-05-14 12:28

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  xuxh120

          來(lái)源 |  urlify.cn/FV3yMj



          一、概述

          1.1 基本概念

          a. 動(dòng)態(tài)編譯(dynamic compilation)指的是“在運(yùn)行時(shí)進(jìn)行編譯”;與之相對(duì)的是事前編譯(ahead-of-time compilation,簡(jiǎn)稱AOT),也叫靜態(tài)編譯(static compilation)。

          b. JIT編譯(just-in-time compilation)狹義來(lái)說(shuō)是當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,因而叫“即時(shí)編譯”。JIT編譯是動(dòng)態(tài)編譯的一種特例。JIT編譯一詞后來(lái)被泛化,時(shí)常與動(dòng)態(tài)編譯等價(jià);但要注意廣義與狹義的JIT編譯所指的區(qū)別。
          c. 自適應(yīng)動(dòng)態(tài)編譯(adaptive dynamic compilation)也是一種動(dòng)態(tài)編譯,但它通常執(zhí)行的時(shí)機(jī)比JIT編譯遲,先讓程序“以某種式”先運(yùn)行起來(lái),收集一些信息之后再做動(dòng)態(tài)編譯。這樣的編譯可以更加優(yōu)化。

          1.2 JVM運(yùn)行原理

           

           

          在部分商用虛擬機(jī)中(如HotSpot),Java程序最初是通過(guò)解釋器(Interpreter)進(jìn)行解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁時(shí),就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼”。為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時(shí),虛擬機(jī)將會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各種層次的優(yōu)化,完成這個(gè)任務(wù)的編譯器稱為即時(shí)編譯器(Just In Time Compiler,下文統(tǒng)稱JIT編譯器)。

          即時(shí)編譯器并不是虛擬機(jī)必須的部分,Java虛擬機(jī)規(guī)范并沒(méi)有規(guī)定Java虛擬機(jī)內(nèi)必須要有即時(shí)編譯器存在,更沒(méi)有限定或指導(dǎo)即時(shí)編譯器應(yīng)該如何去實(shí)現(xiàn)。但是,即時(shí)編譯器編譯性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機(jī)優(yōu)秀與否的最關(guān)鍵的指標(biāo)之一,它也是虛擬機(jī)中最核心且最能體現(xiàn)虛擬機(jī)技術(shù)水平的部分。

          由于Java虛擬機(jī)規(guī)范并沒(méi)有具體的約束規(guī)則去限制即使編譯器應(yīng)該如何實(shí)現(xiàn),所以這部分功能完全是與虛擬機(jī)具體實(shí)現(xiàn)相關(guān)的內(nèi)容,如無(wú)特殊說(shuō)明,我們提到的編譯器、即時(shí)編譯器都是指Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器,虛擬機(jī)也是特指HotSpot虛擬機(jī)。

           

          二、為什么HotSpot虛擬機(jī)要使用解釋器與編譯器并存的架構(gòu)?

          盡管并不是所有的Java虛擬機(jī)都采用解釋器與編譯器并存的架構(gòu),但許多主流的商用虛擬機(jī)(如HotSpot),都同時(shí)包含解釋器和編譯器。解釋器與編譯器兩者各有優(yōu)勢(shì):當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯的時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來(lái)越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率。當(dāng)程序運(yùn)行環(huán)境中內(nèi)存資源限制較大(如部分嵌入式系統(tǒng)中),可以使用解釋器執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來(lái)提升效率。此外,如果編譯后出現(xiàn)“罕見(jiàn)陷阱”,可以通過(guò)逆優(yōu)化退回到解釋執(zhí)行。

           

           2.1 編譯的時(shí)間開(kāi)銷

          解釋器的執(zhí)行,抽象的看是這樣的:

          輸入的代碼 -> [ 解釋器 解釋執(zhí)行 ] -> 執(zhí)行結(jié)果

          而要JIT編譯然后再執(zhí)行的話,抽象的看則是:

          輸入的代碼 -> [ 編譯器 編譯 ] -> 編譯后的代碼 -> [ 執(zhí)行 ] -> 執(zhí)行結(jié)果

          說(shuō)JIT比解釋快,其實(shí)說(shuō)的是“執(zhí)行編譯后的代碼”比“解釋器解釋執(zhí)行”要快,并不是說(shuō)“編譯”這個(gè)動(dòng)作比“解釋”這個(gè)動(dòng)作快。

          JIT編譯再怎么快,至少也比解釋執(zhí)行一次略慢一些,而要得到最后的執(zhí)行結(jié)果還得再經(jīng)過(guò)一個(gè)“執(zhí)行編譯后的代碼”的過(guò)程。所以,對(duì)“只執(zhí)行一次”的代碼而言,解釋執(zhí)行其實(shí)總是比JIT編譯執(zhí)行要快。

          怎么算是“只執(zhí)行一次的代碼”呢?粗略說(shuō),下面兩個(gè)條件同時(shí)滿足時(shí)就是嚴(yán)格的“只執(zhí)行一次”

          1. 只被調(diào)用一次,例如類的構(gòu)造器(class initializer,<clinit>())

          2. 沒(méi)有循環(huán)

          對(duì)只執(zhí)行一次的代碼做JIT編譯再執(zhí)行,可以說(shuō)是得不償失。對(duì)只執(zhí)行少量次數(shù)的代碼,JIT編譯帶來(lái)的執(zhí)行速度的提升也未必能抵消掉最初編譯帶來(lái)的開(kāi)銷。只有對(duì)頻繁執(zhí)行的代碼,JIT編譯才能保證有正面的收益。

           

          2.2 編譯的空間開(kāi)銷

          對(duì)一般的Java方法而言,編譯后代碼的大小相對(duì)于字節(jié)碼的大小,膨脹比達(dá)到10x是很正常的。同上面說(shuō)的時(shí)間開(kāi)銷一樣,這里的空間開(kāi)銷也是,只有對(duì)執(zhí)行頻繁的代碼才值得編譯,如果把所有代碼都編譯則會(huì)顯著增加代碼所占空間,導(dǎo)致“代碼爆炸”。

          這也就解釋了為什么有些JVM會(huì)選擇不總是做JIT編譯,而是選擇用解釋器+JIT編譯器的混合執(zhí)行引擎。

           

          三、為何HotSpot虛擬機(jī)要實(shí)現(xiàn)兩個(gè)不同的即時(shí)編譯器?

          HotSpot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器:Client Complier和Server Complier,簡(jiǎn)稱為C1、C2編譯器,分別用在客戶端和服務(wù)端。目前主流的HotSpot虛擬機(jī)中默認(rèn)是采用解釋器與其中一個(gè)編譯器直接配合的方式工作。程序使用哪個(gè)編譯器,取決于虛擬機(jī)運(yùn)行的模式。HotSpot虛擬機(jī)會(huì)根據(jù)自身版本與宿主機(jī)器的硬件性能自動(dòng)選擇運(yùn)行模式,用戶也可以使用“-client”或“-server”參數(shù)去強(qiáng)制指定虛擬機(jī)運(yùn)行在Client模式或Server模式。

          用Client Complier獲取更高的編譯速度,用Server Complier 來(lái)獲取更好的編譯質(zhì)量。為什么提供多個(gè)即時(shí)編譯器與為什么提供多個(gè)垃圾收集器類似,都是為了適應(yīng)不同的應(yīng)用場(chǎng)景。


          Server Compiler和Client Compiler兩個(gè)編譯器的編譯過(guò)程是不一樣的。

          • 對(duì)Client Compiler來(lái)說(shuō),它是一個(gè)簡(jiǎn)單快速的編譯器,主要關(guān)注點(diǎn)在于局部?jī)?yōu)化,而放棄許多耗時(shí)較長(zhǎng)的全局優(yōu)化手段。

          • 而Server Compiler則是專門面向服務(wù)器端的,并為服務(wù)端的性能配置特別調(diào)整過(guò)的編譯器,是一個(gè)充分優(yōu)化過(guò)的高級(jí)編譯器。

          四、哪些程序代碼會(huì)被編譯為本地代碼?如何編譯為本地代碼?

          程序中的代碼只有是熱點(diǎn)代碼時(shí),才會(huì)編譯為本地代碼,那么什么是熱點(diǎn)代碼呢?運(yùn)行過(guò)程中會(huì)被即時(shí)編譯器編譯的“熱點(diǎn)代碼”有兩類:

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

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

          兩種情況,編譯器都是以整個(gè)方法作為編譯對(duì)象。這種編譯方法因?yàn)榫幾g發(fā)生在方法執(zhí)行過(guò)程之中,因此形象的稱之為棧上替換(On Stack Replacement,OSR),即方法棧幀還在棧上,方法就被替換了。

          4.1 如何判斷方法或一段代碼或是不是熱點(diǎn)代碼呢?

          要知道方法或一段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時(shí)編譯,需要進(jìn)行Hot Spot Detection(熱點(diǎn)探測(cè))。

          目前主要的熱點(diǎn)探測(cè)方式有以下兩種:

          • (1) 基于采樣的熱點(diǎn)探測(cè)

          采用這種方法的虛擬機(jī)會(huì)周期性地檢查各個(gè)線程的棧頂,如果發(fā)現(xiàn)某些方法經(jīng)常出現(xiàn)在棧頂,那這個(gè)方法就是“熱點(diǎn)方法”。這種探測(cè)方法的好處是實(shí)現(xiàn)簡(jiǎn)單高效,還可以很容易地獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開(kāi)即可),缺點(diǎn)是很難精確地確認(rèn)一個(gè)方法的熱度,容易因?yàn)槭艿骄€程阻塞或別的外界因素的影響而擾亂熱點(diǎn)探測(cè)。

          • (2) 基于計(jì)數(shù)器的熱點(diǎn)探測(cè)

          采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法(甚至是代碼塊)建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過(guò)一定的閥值,就認(rèn)為它是“熱點(diǎn)方法”。這種統(tǒng)計(jì)方法實(shí)現(xiàn)復(fù)雜一些,需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器,而且不能直接獲取到方法的調(diào)用關(guān)系,但是它的統(tǒng)計(jì)結(jié)果相對(duì)更加精確嚴(yán)謹(jǐn)。

          4.2 HotSpot虛擬機(jī)中使用的是哪鐘熱點(diǎn)檢測(cè)方式呢?

          HotSpot虛擬機(jī)中使用的是第二種——基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方法,因此它為每個(gè)方法準(zhǔn)備了兩個(gè)計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器。在確定虛擬機(jī)運(yùn)行參數(shù)的前提下,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過(guò)閾值溢出了,就會(huì)觸發(fā)JIT編譯。

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

          顧名思義,這個(gè)計(jì)數(shù)器用于統(tǒng)計(jì)方法被調(diào)用的次數(shù)。
          當(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)求。
          如果不做任何設(shè)置,執(zhí)行引擎并不會(huì)同步等待編譯請(qǐng)求完成,而是繼續(xù)進(jìn)行解釋器按照解釋方式執(zhí)行字節(jié)碼,直到提交的請(qǐng)求被編譯器編譯完成。當(dāng)編譯工作完成之后,這個(gè)方法的調(diào)用入口地址就會(huì)系統(tǒng)自動(dòng)改寫成新的,下一次調(diào)用該方法時(shí)就會(huì)使用已編譯的版本。

           

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

          它的作用就是統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊”。

           

           



          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長(zhǎng)按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天爱天天射天天爽 | 香蕉大伊人 | 国产又粗又硬视频 | 影音先锋啪啪啪 | 国产系列第一页 |