JVM內存結構組成

背景:一談到JVM一直是很多人覺得頭疼的知識點,那麼針對JVM這個痛點,我總結了一些,網上很多談到由淺入深JVM,其實醜話說在前,一篇文章或者幾篇文章是不夠深入JVM的,但至少知其然。

PS:至於知其所以然,依舊還是推薦《深入理解JVM》這本書,雖說它很多還是基於JDK1.7去演示的,但萬變不離其宗。且目前已有更新第三版,完全不用擔心過時。周老師還是很強滴~~

一、JVM內存結構組成

首先我們來看一張圖。由圖我們可得知,JVM組成主要包含 堆、棧、元區間(方法區)、本地方法棧、PC寄存器等。

且JVM內存中包含 棧、本地方法棧、PC寄存器。且在1.8之前是包含方法區的。1.8之後揪出來放在了內存。

 

 

1.1、堆

堆:只要new一個對象,就會存放在堆裏。比如定義一個數組,堆數據所有線程都會共享。在Java虛擬機啓動就建立堆,最主要的內存工作區域,幾乎所有的對象實例都存放到Java堆中。我們可以認爲堆中的變量是持久存在的,而棧的變量是臨時態的。至於老年代新生代垃圾回收後期記載。

比如創建一個數組array:

 

public int[] array = new int[] {1, 2, 3};

那麼該對象array就會存放在堆內存中,被所有線程共享。由此也會引發一個很頭疼的問題,當類A與類B同時操作該數組時,會出現衝突,造成數據錯亂,那麼就產生線程安全問題。讓人頭疼。當然解決的方法也有很多種,JDK中syn鎖,lock鎖等都可以處理,這裏跳過。

 

1.2、棧

棧:棧她由一個個棧幀組成,那麼棧幀是什麼?其實就是一個一個的方法體,比如方法say,就是一個棧幀。

public static void say(String text){
    String remark = "Hello world";
    System.out.println(remark + text);
}

棧幀(方法體):那麼通過方法體來形象的理解棧幀。每個棧幀都包含 局部變量表、操作數棧、動態鏈接、返回鏈接。

  • 局部變量表:用於方法參數和方法內部定義的局部變量。通過索引訪問。Say方法中的text參數與變量remark屬於局部變量
  • 操作數棧:又稱操作棧,通過標準彈棧壓棧進行訪問。比如,如果某個指令把一個值壓入到操作數棧中,稍後另一個指令就可以彈出這個值來使用。看下面的示例,它演示了虛擬機是如何把兩個int類型的局部變量相加,再把結果保存到第三個局部變量的。
begin  
iload_0    // push the int in local variable 0 onto the stack  
iload_1    // push the int in local variable 1 onto the stack  
iadd       // pop two ints, add them, push result  
istore_2   // pop int, store into local variable 2  
end  

 在這個字節碼序列裏,前兩個指令iload_0和iload_1將存儲在局部變量中索引爲0(100)和1(98)的整數壓入操作數棧中,其後iadd指令從操作數棧中彈出那兩個整數相加(100+98),再將結果壓入操作數棧。第四條指令istore_2則從操作數棧中彈出結果(198),並把它存儲到局部變量區索引爲2的位置。下圖詳細表述了這個過程中局部變量和操作數棧的狀態變化,圖中沒有使用的局部變量區和操作數棧區域以空白表示。


看完以上一堆代碼我們再來簡單複述下,它對應的Java代碼實際上就是

public static int add(){
    int i0 = 100;
    int i1 = 98;
    // int i2 = i0 + i1; return i2;
    //198
    return i0 + i1;
}

是不是很簡單?

 

  • 動態鏈接:動態鏈接的概念,就相當於你在say方法中,調用了add方法,來段代碼
public static void print(){
    System.out.println(add());
}
  • 返回鏈接:即指方法運行後,返回某處。以print方法爲栗子,add方法返回鏈接就指向print,而print是無返回void方法,那麼程序會直接運行完畢。若是在main方法中運行print就會返回到main方法,並繼續走完程序。

1.3、本地方法棧

本地方法棧,最大不同爲本地方法棧用於本地方法調用。Java虛擬機允許Java直接調用本地方法(通過使用C語言寫)。

 

上圖說到Native修飾的方法就是本地方法,那麼有哪些呢?舉個最通俗的例子。String類源碼中的intern方法。

public native String intern();

那麼該方法就是個本地方法,至於使用與作用,在下文方法區詳細講解。

 

1.4、元區間(方法區)

方法區主要存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等。

那麼舉個栗子來了解一下方法區

public static void main(String[] args) {
    
    String a = "abc";
    String b = "abc";
    String c = new String("abc");
    String d = "a";
    System.out.println(a == b);//true
    System.out.println(a == c);//false
    System.out.println(a == c.intern());//true
    System.out.println(a == d);//false
}

下面畫圖演示下ABC三者的關係圖

首先根據a、b、c屬於方法內局部變量,那麼就是存放在棧中。其次是方法區字符串常量池中的"abc",是由方法內部創建而來。

至於堆中也會有個"abc",那是由於 c是通過new String()創建的。

細心的朋友會發現,代碼中,a==c爲false,a==c.intern()爲什麼會爲true呢?

 

原因:調用了intern方法的字符串會將堆的值放入到常量池中,原理,被native關鍵字修飾。其實就是將JVM內存中的數據放入了本地內存中。相當於Hashset賦值,堆裏面的數據就不會有了。

通過c.intern後的引用圖狀態。

 

千萬注意!!! JDK1.6及之前是false,JDK1.7及以後爲true。面試如果有指定JDK的坑的intern相關筆試題,就不要弄錯了!

 

1.5、PC寄存器

PC(Program Couneter)寄存器也是每個線程私有的空間, Java虛擬機會爲每個線程創建PC寄存器,在任意時刻,一個Java線程總是在執行一個方法,這個方法稱爲當前方法,如果當前方法不是本地方法,PC寄存器總會執行當前正在被執行的指令,如果是本地方法,則PC寄存器值爲Underfined。

寄存器存放當前執行環境指針、程序技術器、操作棧指針、計算的變量指針等信息。

通俗點說,PC寄存器即爲程序執行位置。

 

 

二、彙編

在上文中的棧幀中提到了彙編的操作,相信大家應該也是迫不及待的想知道原理,也想自己動手操作呢!那麼教程

比如現在要彙編Test類

public class Test {

    /*
            彙編代碼執行
         0: bipush        100   將一個8位帶符號整數壓入棧(這裏是壓入100)
         2: istore_1            將int類型值存入局部變量(其他類型有其他的規則)
         3: bipush        99    同100壓入
         5: istore_2            將int類型值存入局部變量(其他類型有其他的規則)
         6: iload_1             從局部變量中裝載int類型值  (將100裝載到操作數棧)
         7: iload_2             從局部變量中裝載int類型值  (裝99載到操作數棧)
         8: iadd                執行int類型的加法 (99+100)
         9: istore_3            將int類型值存入局部變量(其他類型有其他的規則) 定義給第三個變量 c,存入局部變量
        10: return              方法返回
        */
    public void add(){
        int a = 100;
        int b = 99;
        int c = a + b;
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.add();
    }
}

1.首先我們切到對應目錄輸入命令,注意是當前Test類在的目錄

java -c Test.java

2.我們會發現出現不是處理文件,那麼我們打開設置

4是名字,5是備註都可以更改

4:JavapUser
5:執行Javap命令,我要彙編我不管必須要行
6:$JDKPath$\bin\javap.exe
7:-v $FileClass$
8:$OutputPath$

3.編輯完成後,我們運行命令也不會成功的,至少我不會,那麼如何做呢?

右鍵呼出菜單,選擇External Tools點擊顯示的命令即可

三、總結

JVM內存中主要包含棧(由多個棧幀組成,一個棧幀等於一個方法)、本地方法棧(Native修飾的方法,如String源碼中的intern)、PC寄存器(程序執行位置),JDK1.7之前包含方法區(存放class對象、靜態屬性、常量池等等)。

1.8之後方法區移動到內存中,更名元區間,與堆(存放new對象)共處一室。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章