以前學JVM的時候看過《深入理解JVM》,當時看的很模糊也記了些筆記,更像是為了應付面試。事實是確實把筆記都背上了,春招找實習的時候,記憶體管理、類載入、垃圾回收三連背一遍。後來自己做專案的時候,涉及到JVM的部分還是不怎麼理解,最近重讀了上面的書並且看了一些技術大佬的專欄,用部落格記錄下自己學習過程與思考。
本篇文章關注兩個問題:
-
- Java位元組碼進入JVM後是怎麼儲存的?
為了解釋上面問題,假設現在我們有一個Main類,呼叫compute方法執行計算操作,程式碼如下:
public class Math { public static final Integer CONSTANT = 10; public int compute() { int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math1 = new Math(); Math math2 = new Math(); math1.compute(); math2.compute(); } }
對於第一個問題: Class檔案是一組以8位位元組位基礎的單位的二進位制流,下圖就是顯示瞭如何生成位元組碼檔案。
使用Sublime Text檢視Math.class,圖片只擷取了部分,編輯器是使用16進位制顯示的。為了方便檢視,我們使用 javap -c 指令對程式碼進行反彙編,就可以得得到可讀性更強的檔案。
那麼Class檔案被載入後在JVM中是如何儲存的呢?我們以 HotSpot VM為例,這是目前使用最廣泛的Java虛擬機器。虛擬機器主要由類裝載子系統、執行時資料區和執行引擎三部分組成。JVM記憶體模型將執行時資料區分為五個部分,下面圖中其中紫色部分是執行緒私有的,黃色是執行緒公有的。整個程式碼的執行流程在JVM記憶體中是這樣的:
- 區域性變數表:存放的是方法在執行時各種基本型別和引用型別變數,以及returnAddress型別(指向了一條位元組碼指令的地址);
- 方法出口:儲存的是方法執行完後回到主執行緒的哪個位置。對於main棧幀,區域性變數表裡的math變數存放的是堆記憶體中math變數的地址。
- 運算元棧:臨時存放方法執行時的變數
- 動態連結:Class 檔案中存放了大量的符號引用,這些符號引用指向的是方法。程式執行期間呼叫方法時,根據執行時常量池的引數,靜態符號引用變成直接引用;物件頭裡的指標會動態的找到方法區中儲存的呼叫方法的資訊。
程式計數器:記錄的是位元組碼指令正在執行或者即將執行的行號,比如這行 ”0: iconst_1“執行完了,程式計數器值就是1,表示即將執行下一行指令。
本地方法棧:作用和虛擬機器棧類似,為native修飾的方法服務。
方法區:JDK1.8及以後稱為元空間,儲存被虛擬機器載入的類資訊、常量、靜態變數等。1.8以後方法區使用的是本機的記憶體。例如 這一行指令 ”public static final java.lang.Integer CONSTANT;“就是靜態常量 CONSTANT的資訊。
堆:堆是JVM記憶體模型中最大的一塊,虛擬機器啟動時就會建立,儲存的是大部分物件。
參考資料:《深入理解Java虛擬機器》第二版 周志朋
《深入拆解Java虛擬機器》鄭雨迪
《JVM虛擬機器底層原理分析與效能調優》程式設計師諸葛