一、JVM 架構基礎
JVM 進程啟動時,ClassLoader 會將需要的所有類加載到內存,主要分為以下三步:
- Bootstrap Class: 核心類庫,由 “Bootstrap Class Loader”負責加載, 例如基礎的運行時類庫 JRE\lib\rt.jar。
- Extension Class: java.ext.dirs 路徑下的類,由 ExtClassLoader 負責加載。在實際開發中,如果需要添加額外的類庫,通常放置於此位置。
- Application Class: 實際應用包含的類,由 AppClassLoader 負責加載。
二、JVM 預熱是指什么?
類加載過程完畢后,所有需要的類會進入 JVM cache (native code) ,這樣就可以被快速的實時訪問。當然,還有許多其它與JVM啟動無關的類此時並未被加載。
當應用的第一個請求到來,會觸發邏輯相關類的第一次加載,此過程會有一定的耗時,會影響第一次調用的實時響應。這主要是因為JVM的懶加載及JIT機制。因此對於低延遲應用,必須采用特定的策略來處理第一次的預加載邏輯,以保障第一次的請求的快速響應。此過程,我們稱之為 JVM 的預熱。
三、Tiered Compilation
JVM 即時編譯機制會將使用頻率較高的方法或者代碼塊兒編譯優化放入本地緩存。以提高程序響應速度。基於此,我們可以通過在應用啟動之初,強制加載我們預先認知的高頻方法。相應設置參數包括如下:
-XX:+TieredCompilation:開啟分層編譯(1.7 Server 模式默認開啟)
-XX:CompileThreshold:設置觸發即時編譯閾值
-XX:+PrintCompilation:打印被編譯成本地代碼的方法名稱
通常虛擬機會通過解釋器來收集反饋到編譯器的方法調用信息。
附:解釋器 & 編譯器
解釋器:快速啟動,執行
編譯器:將熱點方法及代碼塊兒編譯成本地代碼,提高執行效率
即時編譯器編譯本地代碼需要占用程序運行時間,同時,為了編譯出高效的本地代碼,解釋器需要收集相應的性能監控信息(基於采樣、計數器熱點探測)供編譯器使用,從而影響解釋器的執行速度。
分層編譯:平衡程序啟動相應速度及運行效率
C0:解釋執行,不進行編譯器編譯
C1:將字節碼編譯為本地代碼,進行簡單可靠的優化,必要時加入性能監控邏輯
C2:將字節碼編譯為本地代碼,基於性能監控,啟用一些優化程度更高,編譯耗時更長的優化。
四、自定義實現
基於上一小節所述,我們可以額外實現特定邏輯來進行特定方法的多次調用(-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 }
將需要預熱的業務邏輯放置於預熱處理方法內。