不可不知的 JVM 預熱

WindWant發表於2021-02-23

一、JVM 架構基礎

JVM 程式啟動時,ClassLoader 會將需要的所有類載入到記憶體,主要分為以下三步:

  • Bootstrap Class: 核心類庫,由 “Bootstrap Class Loader負責載入, 例如基礎的執行時類庫 JRE\lib\rt.jar
  • Extension Classjava.ext.dirs 路徑下的類,由 ExtClassLoader 負責載入。在實際開發中,如果需要新增額外的類庫,通常放置於此位置。
  • Application Class實際應用包含的類,由 AppClassLoader 負責載入。

二、JVM 預熱是指什麼?

類載入過程完畢後,所有需要的類會進入 JVM cache (native code) ,這樣就可以被快速的實時訪問。當然,還有許多其它與JVM啟動無關的類此時並未被載入。

當應用的第一個請求到來,會觸發邏輯相關類的第一次載入,此過程會有一定的耗時,會影響第一次呼叫的實時響應。這主要是因為JVM的懶載入及JIT機制。因此對於低延遲應用,必須採用特定的策略來處理第一次的預載入邏輯,以保障第一次的請求的快速響應。此過程,我們稱之為 JVM 的預熱。

三、Tiered Compilation

 JVM會將使用頻率較高的方法放入本地快取。以達到快速呼叫響應的目的。基於此,我們可以通過在應用啟動之初,強制載入我們預先認知的高頻方法。設定引數包括如下:

-XX:CompileThreshold -XX:TieredCompilation

通常虛擬機器會通過直譯器來收集反饋到編譯器的方法呼叫資訊。

四、自定義實現

基於上一小節所述,我們可以額外實現特定邏輯來進行特定方法的多次呼叫(-XX:CompileThreshold),以觸發JVM的編譯。如下示例:

首先,我們定義一個包含基礎方法的類:

public class Dummy {

    public void m() {

    }
}

 

其次,我們建立一個載入類,在其內部新增靜態方法,迴圈100000次重複生成Dummy物件,並呼叫其方法:

public class ManualClassLoader {

    protected static void load() {
        for (int i = 0; i < 100000; i++) {
            Dummy dummy = new Dummy();
            dummy.m();
        }
    }
}

現在我們使用如下過程,測試效能:

public class MainApplication {

    static {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Warm Up time : " + (end - start));
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Total time taken : " + (end - start));
    }

}

 如下為測試結果:

預熱之後

未預熱

差別(%)

1220056

8903640

730

1083797

13609530

1256

1026025

9283837

905

1024047

7234871

706

868782

9146180

1053

預熱之後的效能明顯好於未預熱狀態下的呼叫。

當然,這裡只是一個簡單的示例測試,具體到實際的應用中,還需要考慮特定的業務邏輯需求。

五、工具

通常用於基準測試,基本使用如下:

依賴 pom.xml:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
</dependency>

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
</dependency>

maven庫連線: Central Maven Repository

定義預熱處理方法,並新增@Benchmark註解:

@Benchmark
public void init() {

    //code todo

}

將需要預熱的業務邏輯放置於預熱處理方法內。

 

相關文章