JVM即時編譯(JIT)

2022-09-06 23:30:30 字數 3743 閱讀 1398

1、動態編譯(dynamic compilation)指的是「在執行時進行編譯」;與之相對的是事前編譯(ahead-of-time compilation,簡稱aot),也叫靜態編譯(static compilation)。

2、jit編譯(just-in-time compilation)狹義來說是當某段**即將第一次被執行時進行編譯,因而叫「即時編譯」。jit編譯是動態編譯的一種特例。jit編譯一詞後來被泛化,時常與動態編譯等價;但要注意廣義與狹義的jit編譯所指的區別。

3、自適應動態編譯(adaptive dynamic compilation)也是一種動態編譯,但它通常執行的時機比jit編譯遲,先讓程式「以某種式」先執行起來,收集一些資訊之後再做動態編譯。這樣的編譯可以更加優化。

jvm執行原理

在部分商用虛擬機器中(如hotspot),j**a程式最初是通過直譯器(interpreter)進行解釋執行的,當虛擬機器發現某個方法或**塊的執行特別頻繁時,就會把這些**認定為「熱點**」。為了提高熱點**的執行效率,在執行時,虛擬機器將會把這些**編譯成與本地平台相關的機器碼,並進行各種層次的優化,完成這個任務的編譯器稱為即時編譯器(just in time compiler,下文統稱jit編譯器)。

即時編譯器並不是虛擬機器必須的部分,j**a虛擬機器規範並沒有規定j**a虛擬機器內必須要有即時編譯器存在,更沒有限定或指導即時編譯器應該如何去實現。但是,即時編譯器編譯效能的好壞、**優化程度的高低卻是衡量一款商用虛擬機器優秀與否的最關鍵的指標之一,它也是虛擬機器中最核心且最能體現虛擬機器技術水平的部分。

由於j**a虛擬機器規範並沒有具體的約束規則去限制即使編譯器應該如何實現,所以這部分功能完全是與虛擬機器具體實現相關的內容,如無特殊說明,我們提到的編譯器、即時編譯器都是指hotspot虛擬機器內的即時編譯器,虛擬機器也是特指hotspot虛擬機器。

儘管並不是所有的j**a虛擬機器都採用直譯器與編譯器並存的架構,但許多主流的商用虛擬機器(如hotspot),都同時包含直譯器和編譯器。直譯器與編譯器兩者各有優勢:當程式需要迅速啟動和執行的時候,直譯器可以首先發揮作用,省去編譯的時間,立即執行。在程式執行後,隨著時間的推移,編譯器逐漸發揮作用,把越來越多的**編譯成本地**之後,可以獲取更高的執行效率。當程式執行環境中記憶體資源限制較大(如部分嵌入式系統中),可以使用直譯器執行節約記憶體,反之可以使用編譯執行來提公升效率。此外,如果編譯後出現「罕見陷阱」,可以通過逆優化退回到解釋執行。

直譯器的執行,抽象的看是這樣的:

輸入的** -> [ 直譯器 解釋執行 ] -> 執行結果

而要jit編譯然後再執行的話,抽象的看則是:

輸入的** -> [ 編譯器 編譯 ] -> 編譯後的** -> [ 執行 ] -> 執行結果

說jit比解釋快,其實說的是「執行編譯後的**」比「直譯器解釋執行」要快,並不是說「編譯」這個動作比「解釋」這個動作快。

jit編譯再怎麼快,至少也比解釋執行一次略慢一些,而要得到最後的執行結果還得再經過乙個「執行編譯後的**」的過程。

所以,對「只執行一次」的**而言,解釋執行其實總是比jit編譯執行要快。

怎麼算是「只執行一次的**」呢?粗略說,下面兩個條件同時滿足時就是嚴格的「只執行一次」

1、只被呼叫一次,例如類的構造器(class initializer,())

2、沒有迴圈

對只執行一次的**做jit編譯再執行,可以說是得不償失。

對只執行少量次數的**,jit編譯帶來的執行速度的提公升也未必能抵消掉最初編譯帶來的開銷。

只有對頻繁執行的**,jit編譯才能保證有正面的收益。

這也就解釋了為什麼有些jvm會選擇不總是做jit編譯,而是選擇用直譯器+jit編譯器的混合執行引擎。

hotspot虛擬機器中內建了兩個即時編譯器:client complier和server complier,簡稱為c1、c2編譯器,分別用在客戶端和服務端。目前主流的hotspot虛擬機器中預設是採用直譯器與其中乙個編譯器直接配合的方式工作。程式使用哪個編譯器,取決於虛擬機器執行的模式。hotspot虛擬機會根據自身版本與宿主機器的硬體效能自動選擇執行模式,使用者也可以使用「-client」或「-server」引數去強制指定虛擬機器執行在client模式或server模式。

用client complier獲取更高的編譯速度,用server complier 來獲取更好的編譯質量。為什麼提供多個即時編譯器與為什麼提供多個垃圾收集器類似,都是為了適應不同的應用場景。

程式中的**只有是熱點**時,才會編譯為本地**,那麼什麼是熱點**呢?

執行過程中會被即時編譯器編譯的「熱點**」有兩類:

1、被多次呼叫的方法。

2、被多次執行的迴圈體。

兩種情況,編譯器都是以整個方法作為編譯物件。 這種編譯方法因為編譯發生在方法執行過程之中,因此形象的稱之為棧上替換(on stack replacement,osr),即方法棧幀還在棧上,方法就被替換了。

要知道方法或一段**是不是熱點**,是不是需要觸發即時編譯,需要進行hot spot detection(熱點探測)。

目前主要的熱點探測方式有以下兩種:

(1)基於取樣的熱點探測

採用這種方法的虛擬機會周期性地檢查各個執行緒的棧頂,如果發現某些方法經常出現在棧頂,那這個方法就是「熱點方法」。這種探測方法的好處是實現簡單高效,還可以很容易地獲取方法呼叫關係(將呼叫堆疊展開即可),缺點是很難精確地確認乙個方法的熱度,容易因為受到執行緒阻塞或別的外界因素的影響而擾亂熱點探測。

(2)基於計數器的熱點探測

採用這種方法的虛擬機會為每個方法(甚至是**塊)建立計數器,統計方法的執行次數,如果執行次數超過一定的閥值,就認為它是「熱點方法」。這種統計方法實現複雜一些,需要為每個方法建立並維護計數器,而且不能直接獲取到方法的呼叫關係,但是它的統計結果相對更加精確嚴謹。

在hotspot虛擬機器中使用的是第二種——基於計數器的熱點探測方法,因此它為每個方法準備了兩個計數器:方法呼叫計數器和回邊計數器。在確定虛擬機器執行引數的前提下,這兩個計數器都有乙個確定的閾值,當計數器超過閾值溢位了,就會觸發jit編譯。

顧名思義,這個計數器用於統計方法被呼叫的次數。

當乙個方法被呼叫時,會先檢查該方法是否存在被jit編譯過的版本,如果存在,則優先使用編譯後的本地**來執行。如果不存在已被編譯過的版本,則將此方法的呼叫計數器值加1,然後判斷方法呼叫計數器與回邊計數器值之和是否超過方法呼叫計數器的閾值。如果超過閾值,那麼將會向即時編譯器提交乙個該方法的**編譯請求。

如果不做任何設定,執行引擎並不會同步等待編譯請求完成,而是繼續進行直譯器按照解釋方式執行位元組碼,直到提交的請求被編譯器編譯完成。當編譯工作完成之後,這個方法的呼叫入口位址就會系統自動改寫成新的,下一次呼叫該方法時就會使用已編譯的版本。

它的作用就是統計乙個方法中迴圈體**執行的次數,在位元組碼中遇到控制流向後跳轉的指令稱為「回邊」。

server compiler和client compiler兩個編譯器的編譯過程是不一樣的。

對client compiler來說,它是乙個簡單快速的編譯器,主要關注點在於區域性優化,而放棄許多耗時較長的全域性優化手段。

而server compiler則是專門面向伺服器端的,並為服務端的效能配置特別調整過的編譯器,是乙個充分優化過的高階編譯器。

JVM編譯優化

參考文章 整體描述 標量替換 虛擬機會用即時編譯器把執行頻繁的熱點 變成機器碼,並做相應優化。1 直譯器和編譯器的優點 直譯器優點 程式啟動快,占用記憶體小,編譯失敗時還能逆優化恢復到解析狀態 編譯器優點 執行時可探測熱點 並把位元組碼編譯成本地機器碼,這樣程式執行效率更高 2 在什麼情況下才會被判...

Android 系統(90) JIT 編譯器

儘管 jit 和 aot 使用相同的編譯器,它們所進行的一系列優化也較為相似,但它們生成的 可能會有所不同。jit 會利用執行時型別資訊,可以更高效地進行內聯,並可讓堆疊替換 osr 編譯成為可能,而這一切都會使其生成的 略有不同。使用者執行應用,而這隨後就會觸發 art 載入.dex檔案。oat檔...

php8 0 編譯安裝以及開啟 JIT

使用centos7.5作為環境 安裝相關依賴 yum install libxml2 libxml2 devel openssl openssl devel bzip2 bzip2 devel libcurl libcurl devel libjpeg libjpeg devel libpng li...