再探JVM記憶體模型
以前學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虛擬機底層原理分析與性能調優》程式設計師諸葛