再探JVM記憶體模型

今天你做題了嗎發表於2020-07-02

  以前學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虛擬機器底層原理分析與效能調優》程式設計師諸葛 

相關文章