再探JVM記憶體模型

  以前學JVM的時候看過《深入理解JVM》,當時看的很模糊也記了些筆記,更像是為了應付面試。事實是確實把筆記都背上了,春招找實習的時候,記憶體管理、類載入、垃圾回收三連背一遍。後來自己做項目的時候,涉及到JVM的部分還是不怎麼理解,最近重讀了上面的書並且看了一些技術大佬的專欄,用部落格記錄下自己學習過程與思考。

本篇文章關注兩個問題:

  1. Java位元組碼是什麼?Java源程式碼怎麼變成Java位元組碼的?

  2. 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記憶體中是這樣的:

  

  我們對著位元組碼文件來闡述。虛擬機又叫做執行緒棧,生命周期與執行緒相同。棧主要由局部變數表、操作數棧、動態鏈接、方法出口組成。當main方法運行時JVM會在棧記憶體區域給主執行緒分配一塊記憶體,main方法和compute方法執行時,會創建單獨的棧幀用於存儲方法的一些資訊。

  • 局部變數表:存放的是方法在執行時各種基本類型和引用類型變數,以及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虛擬機底層原理分析與性能調優》程式設計師諸葛 

Tags: