Java虛擬機是怎樣運行Java字節碼的?

以標準 JDK 中的 HotSpot 虛擬機爲例,從虛擬機以及底層硬件兩個角度來看Java 虛擬機具體是怎麼運行 Java 字節碼的。

 

虛擬機視角

從虛擬機視角來看,執行 Java 代碼首先需要將它編譯而成的 class 文件加載到 Java 虛擬機中。

加載後的 Java 類會被存放於方法區(Method Area)中。實際運行時,虛擬機會執行方法區內的代碼。

在運行過程中,每當調用進入一個 Java 方法,Java 虛擬機會在當前線程的 Java 方法棧中生成一個棧幀,用以存放局部變量以及字節碼的操作數。

這個棧幀的大小是提前計算好的,而且 Java 虛擬機不要求棧幀在內存空間裏連續分佈。 

當退出當前執行的方法時,不管是正常返回還是異常返回,Java 虛擬機均會彈出當前線程的當前棧幀,並將之捨棄。

 

硬件視角

從硬件視角來看,Java 字節碼無法直接執行。因此,Java 虛擬機需要將字節碼翻譯成機器碼。

在 HotSpot 裏面,上述翻譯過程有兩種形式:第一種是解釋執行,即逐條將字節碼翻譯成機器碼並執行;

第二種是即時編譯(Just-In-Time compilation,JIT),即將一個方法中包含的所有字節碼編譯成機器碼後再執行。

前者的優勢在於無需等待編譯,而後者的優勢在於實際運行速度更快。

 HotSpot 默認採用混合模式,綜合瞭解釋執行和即時編譯兩者的優點。

它會先解釋執行字節碼,而後將其中反覆執行的熱點代碼,以方法爲單位進行即時編譯。

 

即時編譯

即時編譯建立在程序符合二八定律的假設上,也就是百分之二十的代碼佔據了百分之八十的計算資源。

對於佔據大部分的不常用的代碼,我們無需耗費時間將其編譯成機器碼,而是採取解釋執行的方式運行;

另一方面,對於僅佔據小部分的熱點代碼,我們則可以將其編譯成機器碼,以達到理想的運行速度。

 

爲了滿足不同用戶場景的需要,HotSpot 內置了多個即時編譯器:C1、C2 和 Graal。

Graal 是 Java 10 正式引入的實驗性即時編譯器,這裏暫不做討論。

 

之所以引入多個即時編譯器,是爲了在編譯時間和生成代碼的執行效率之間進行取捨。

 

C1 又叫做 Client 編譯器,面向的是對啓動性能有要求的客戶端 GUI 程序,採用的優化手段相對簡單,因此編譯時間較短。

C2 又叫做 Server 編譯器,面向的是對峯值性能有要求的服務器端程序,採用的優化手段相對複雜。

因此編譯時間較長,但同時生成代碼的執行效率較高。

從 Java 7 開始,HotSpot 默認採用分層編譯的方式:熱點方法首先會被 C1 編譯,而後熱點方法中的熱點會進一步被 C2 編譯。

爲了不干擾應用的正常運行,HotSpot 的即時編譯是放在額外的編譯線程中進行的。

HotSpot 會根據 CPU 的數量設置編譯線程的數目,並且按 1:2 的比例配置給 C1 及 C2 編譯器。

在計算資源充足的情況下,字節碼的解釋執行和即時編譯可同時進行。

編譯完成後的機器碼會在下次調用該方法時啓用,以替換原本的解釋執行。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章