jit編譯

熱點代碼:編程

虛擬機中的字節碼(.class文件內容)是由解釋器( Interpreter )完成編譯的,當虛擬機發現某個方法或代碼塊的運行特別頻繁的時候,就會把這些代碼認定爲「熱點代碼」。服務器

 

什麼是jit編譯:app

爲了提升熱點代碼的執行效率,在運行時,即時編譯器(JIT)會把這些代碼編譯成與本地平臺相關的機器碼,並進行各層次的優化,而後保存到內存中。編程語言

在 HotSpot 虛擬機中,內置了兩個 JIT,分別爲 C1 編譯器和 C2 編譯器,這兩個編譯器的編譯過程是不同的。模塊化

C1 編譯器是一個簡單快速的編譯器,主要的關注點在於局部性的優化,適用於執行時間較短或對啓動性能有要求的程序,例如,GUI 應用對界面啓動速度就有必定要求。性能

C2 編譯器是爲長期運行的服務器端應用程序作性能調優的編譯器,適用於執行時間較長或對峯值性能有要求的程序。根據各自的適配性,這兩種即時編譯也被稱爲 Client Compiler 和 Server Compiler。優化

 

如何探測代碼爲熱點代碼:ui

熱點探測是基於計數器的熱點探測,採用這種方法的虛擬機會爲每一個方法創建計數器統計方法的執行次數,若是執行次數超過必定的閾值就認爲它是「熱點方法」。線程

虛擬機爲每一個方法準備了兩類計數器:方法調用計數器(Invocation Counter)和回邊計數器(Back Edge Counter;在程序中遇到控制流向後跳轉的指令稱爲「回邊」)。code

 

jit編譯優化技術:

1. 方法內聯

調用一個方法一般要經歷壓棧和出棧,這種執行操做要求在執行前保護現場並記憶執行的地址,並按原來保存的地址返回執行。 所以,方法調用會產生必定的時間和空間方面的開銷。

方法內聯的優化行爲就是把目標方法的代碼複製到發起調用的方法之中,避免發生真實的方法調用。

ps:但要強調一點,熱點方法不必定會被 JVM 作內聯優化,若是這個方法體太大了,JVM 將不執行內聯操做。而方法體的大小閾值,咱們也能夠經過參數設置

2. 逃逸分析

逃逸分析(Escape Analysis)是判斷一個對象是否被外部方法引用或外部線程訪問的分析技術,編譯器會根據逃逸分析的結果對代碼進行優化。

2.1.棧上分配

咱們知道,在 Java 中默認建立一個對象是在堆中分配內存的,而當堆內存中的對象再也不使用時,則須要經過垃圾回收機制回收,這個過程相對分配在棧中的對象的建立和銷燬來講,更消耗時間和性能。這個時候,逃逸分析若是發現一個對象只在方法中使用,就會將對象分配在棧上。

2.2.鎖消除

StringBuffer 中的 append 方法被 Synchronized 關鍵字修飾,會使用到鎖,從而致使性能降低。

但實際上,一個局部方法內使用StringBuffer 和 StringBuilder 的性能基本沒什麼區別。這是由於在局部方法中建立的對象只能被當前線程訪問,沒法被其它線程訪問,這個變量的讀寫確定不會有競爭,這個時候 JIT 編譯會對這個對象的方法鎖進行鎖消除。

2.3.標量替換

逃逸分析證實一個對象不會被外部訪問,若是這個對象能夠被拆分的話,當程序真正執行的時候可能不建立這個對象,而直接建立它的成員變量來代替。將對象拆分後,能夠分配對象的成員變量在棧或寄存器上,本來的對象就無需分配內存空間了。這種編譯優化就叫作標量替換。

如:

public void foo() {
TestInfo info = new TestInfo();
info.id = 1;
info.count = 99;
...//to do something
}

逃逸分析後,代碼會被優化爲:

public void foo() {
id = 1;
count = 99;
...//to do something
}

 

在 Java8 以前,HotSpot 集成了兩個 JIT,用 C1 和 C2 來完成 JVM 中的即時編譯。雖然 JIT 優化了代碼,但收集監控信息會消耗運行時的性能,且編譯過程會佔用程序的運行時間。

到了 Java9,AOT 編譯器被引入。和 JIT 不一樣,AOT 是在程序運行前進行的靜態編譯,這樣就能夠避免運行時的編譯消耗和內存消耗,且 .class 文件經過 AOT 編譯器是能夠編譯成 .so 的二進制文件的。

到了 Java10,一個新的 JIT 編譯器 Graal 被引入。Graal 是一個以 Java 爲主要編程語言、面向 Java bytecode 的編譯器。與用 C++ 實現的 C1 和 C2 相比,它的模塊化更加明顯,也更容易維護。Graal 既能夠做爲動態編譯器,在運行時編譯熱點方法;也能夠做爲靜態編譯器,實現 AOT 編譯。

相關文章
相關標籤/搜索