- JRE(Java 運作時環境)僅包含運作 Java 程式的必需元件,包括 Java 虛拟機以及 Java 核心類庫等。
- JDK(Java 開發工具包)同樣包含了 JRE,并且還附帶了一系列開發、診斷工具。
為了能運作Java,目前的主流思路是設計一個面向 Java 語言特性的虛拟機,并通過編譯器将 Java 程式轉換成該虛拟機所能識别的指令序列,也稱 Java 位元組碼。之是以這麼取名,是因為 Java 位元組碼指令的操作碼(opcode)被固定為一個位元組。
Java 虛拟機可以由硬體實作 [1],但更為常見的是在各個現有平台(如 Windows_x64、Linux_aarch64)上提供軟體實作。
C++的政策是直接編譯成目标架構的機器碼,Java的政策是編譯成一個虛拟架構的機器碼。
使用JVM的好處
一旦一個程式被轉換成 Java 位元組碼,那麼它便可以在不同平台上的虛拟機實作裡運作。即 “一次編寫,到處運作” 。
JVM帶來了一個托管環境(Managed Runtime)。這個托管環境提供了自動記憶體管理與垃圾回收,以及諸如數組越界、動态類型、安全權限等等的動态檢測,使我們免于書寫這些無關業務邏輯的代碼。
JVM運作Java位元組碼
虛拟機視角
執行 Java 代碼首先需要将它編譯而成的 class 檔案加載到 Java 虛拟機中。加載後的 Java 類會被存放于方法區(Method Area)中。實際運作時,虛拟機會執行方法區内的代碼。
Java 虛拟機在記憶體中劃分出堆和棧來存儲運作時資料。
Java 虛拟機會将棧細分為面向 Java 方法的 Java 方法棧,面向本地方法(用 C++ 寫的 native 方法)的本地方法棧,以及存放各個線程執行位置的 PC 寄存器。
在運作過程中,每當調用進入一個 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 編譯器。
在計算資源充足的情況下,位元組碼的解釋執行和即時編譯可同時進行。編譯完成後的機器碼會在下次調用該方法時啟用,以替換原本的解釋執行。
參考資料
深入拆解Java虛拟機
asmtools.jar