JVM中集成了兩種編譯器,Client Compiler和Server Compiler,它們的作用也不同。Client Compiler註重啟動速度和局部的優化,Server Compiler則更加關註全局的優化,性能會更好,但由於會進行更多的全局分析,所以啟動速度會變慢。兩種編譯器有著不同的應用 ...
JVM中集成了兩種編譯器,Client Compiler和Server Compiler,它們的作用也不同。Client Compiler註重啟動速度和局部的優化,Server Compiler則更加關註全局的優化,性能會更好,但由於會進行更多的全局分析,所以啟動速度會變慢。兩種編譯器有著不同的應用場景,在虛擬機中同時發揮作用。
Client Compiler
HotSpot VM帶有一個Client Compiler C1編譯器。這種編譯器啟動速度快,但是性能比較Server Compiler來說會差一些。C1會做三件事:
- 局部簡單可靠的優化,比如位元組碼上進行的一些基礎優化,方法內聯、常量傳播等,放棄許多耗時較長的全局優化。
- 將位元組碼構造成高級中間表示(High-level Intermediate Representation,以下稱為HIR),HIR與平臺無關,通常採用圖結構,更適合JVM對程式進行優化。
- 最後將HIR轉換成低級中間表示(Low-level Intermediate Representation,以下稱為LIR),在LIR的基礎上會進行寄存器分配、窺孔優化(局部的優化方式,編譯器在一個基本塊或者多個基本塊中,針對已經生成的代碼,結合CPU自己指令的特點,通過一些認為可能帶來性能提升的轉換規則或者通過整體的分析,進行指令轉換,來提升代碼性能)等操作,最終生成機器碼。
Server Compiler
Server Compiler主要關註一些編譯耗時較長的全局優化,甚至會還會根據程式運行的信息進行一些不可靠的激進優化。這種編譯器的啟動時間長,適用於長時間運行的後臺程式,它的性能通常比Client Compiler高30%以上。目前,Hotspot虛擬機中使用的Server Compiler有兩種:C2和Graal。
C2 Compiler
在Hotspot VM中,預設的Server Compiler是C2編譯器。
C2編譯器在進行編譯優化時,會使用一種控制流與數據流結合的圖數據結構,稱為Ideal Graph。 Ideal Graph表示當前程式的數據流向和指令間的依賴關係,依靠這種圖結構,某些優化步驟(尤其是涉及浮動代碼塊的那些優化步驟)變得不那麼複雜。
Ideal Graph的構建是在解析位元組碼的時候,根據位元組碼中的指令向一個空的Graph中添加節點,Graph中的節點通常對應一個指令塊,每個指令塊包含多條相關聯的指令,JVM會利用一些優化技術對這些指令進行優化,比如Global Value Numbering、常量摺疊等,解析結束後,還會進行一些死代碼剔除的操作。生成Ideal Graph後,會在這個基礎上結合收集的程式運行信息來進行一些全局的優化,這個階段如果JVM判斷此時沒有全局優化的必要,就會跳過這部分優化。
無論是否進行全局優化,Ideal Graph都會被轉化為一種更接近機器層面的MachNode Graph,最後編譯的機器碼就是從MachNode Graph中得的,生成機器碼前還會有一些包括寄存器分配、窺孔優化等操作。關於Ideal Graph和各種全局的優化手段會在後面的章節詳細介紹。Server Compiler編譯優化的過程如下圖所示:
Graal Compiler
從JDK 9開始,Hotspot VM中集成了一種新的Server Compiler,Graal編譯器。相比C2編譯器,Graal有這樣幾種關鍵特性:
- 前文有提到,JVM會在解釋執行的時候收集程式運行的各種信息,然後編譯器會根據這些信息進行一些基於預測的激進優化,比如分支預測,根據程式不同分支的運行概率,選擇性地編譯一些概率較大的分支。Graal比C2更加青睞這種優化,所以Graal的峰值性能通常要比C2更好。
- 使用Java編寫,對於Java語言,尤其是新特性,比如Lambda、Stream等更加友好。
- 更深層次的優化,比如虛函數的內聯、部分逃逸分析等。
Graal編譯器可以通過Java虛擬機參數-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler啟用。當啟用時,它將替換掉HotSpot中的C2編譯器,並響應原本由C2負責的編譯請求。
參考原文:基本功 | Java即時編譯器原理解析及實踐 - 美團技術團隊 (meituan.com)