JVM內存模型


​ JVM(Java Virtual Machine)又被分為三大子系統,類加載子系統,運行時數據區,執行引擎。在這里我們主要講解一下JVM的運行時數據區,也就是我們常說的JVM存儲數據的內存模型。在這里提一點,平常我們常說內存模型,其實在Java中存在兩大內存模型,一個是JVM的內存模型,也就是堆,棧之類的。還有一個是Java線程工作的內存模型,java工作的內存模型指的是主內存,工作內存,兩個是不同的概念

JVM的結構圖

​ 這里重點講一下JVM的運行時數據區,也就是我們所說的JVM內存模型。其實像JVM結構中的類加載子系統,也能夠講出很多東西出來。

​ 首先,JVM的內存模型分為線程私有,線程共享的區域

線程私有部分

​ 圖中所畫的部分程序計數器,虛擬機棧,本地方法棧是每個線程私有的部分,大概講述一下每個部分所起到的作用

程序計數器:線程在運行期間,由於會出發CPU時間片資源搶奪的情況,假如線程A執行到if判斷,循環,異常處理等這些字節碼指令,時間片突然被搶占,出現阻塞,程序計數器會幫助記錄每個線程所執行到下一條字節碼指令,等線程A再次拿到時間片繼續執行。

虛擬機棧:每個線程在執行每個方法時,都會創建一個棧幀,棧幀里面又會包括一些局部變量表,操作數棧,方法出口等信息,每個方法從執行到執行完成,也就是完成我們的入棧和出棧過程

​ 首先看一段代碼,來講解棧幀中每個部分的作用

public class Test {

    public static void test() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
    }

    public static void main(String[] args) {
        test();
    }
}

​ 以上代碼對應的虛擬棧圖

​ 局部變量表:在我們程序中,方法中定義的一些局部變量都會存放在我們的局部變量表,也就是我們代碼中的a,b,c這種局部變量

​ 將上述代碼用javap -c Test命令可以查看到程序的字節碼指令

public class com.ezhiyang.crm.config.Test {
  public com.ezhiyang.crm.config.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void test();
    Code:
       0: iconst_1  // 將一個常量1加載到操作數棧
       1: istore_0  // 將一個數值(也就是常量1)從操作數棧存儲到局部變量					   表,也就是我們代碼中的int a = 1
       2: iconst_2
       3: istore_1
       4: iload_0
       5: iload_1
       6: iadd		// 執行加法操作
       7: bipush    // 將10入操作數棧    10
       9: imul		// 執行乘法操作
      10: istore_2  // 再將乘法出來的結果從操作數棧存儲到局部變量					   	   表,也就是我們的int c = (a + b) * 10
      11: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method test:()V
       3: return
}

​ 操作數棧:像我們的程序后面都會被編譯成這種字節碼指令,而操作數棧將變量之間的運算入棧,然后存儲計算結果,再出棧賦值給局部變量表

​ 方法出口:代碼中main()方法調用了test()方法,這時test()執行完成后,需要繼續執行main()方法,方法出口記錄了test()方法執行完成后的一個出口,也就是回到main()

本地方法棧:在Java代碼中我們有時可以看到用native修飾的方法,而這些方法並不由Java語言去實現,而是由Java去調用底層的C++語言實現的,跟我們的虛擬機棧有點類似,只不過是執行native方法的線程棧幀

線程共享部分

​ 線程共享的部分就是我們圖中所畫的堆,方法區(又叫做永久代,JDK1.8后被改為元空間)

​ 看一張方法區內存分布圖

方法區:方法區主要存儲的是虛擬機加載的類信息(版本,字段,方法,接口等信息),成員常量,靜態變量等數據,其中又包括一部分是運行時常量池,運行時常量池存放的是編譯期間生成的符號引用,后面經過解析出來的直接引用也會儲存在常量池中。

​ 當虛擬機new指令時,首先會根據這個指令的參數在常量池中定位到一個類的符號引用,如果定位不到,則需要重新對這個類進行加載,解析和初始化了,這里就涉及了Class文件加載的7大過程。如果能找到符號引用,證明這個類已經加載過,並會給該對象分配內存空間,調用構造方法進行初始化。也就是我們new一個對象發生了那些事

​ JDK1.8后,方法區被叫做元空間,並且內存大小限制不限JVM的內存大小限制,而是直接使用計算機的直接內存,受計算機的內存大小限制

:堆是JVM中最大的一塊內存區域了,主要存放我們程序中new出來的對象,提一點並非所有的對象都是在堆上進行分配,隨着線程逃逸,標量替換等技術的發展,對象也有可能在棧上進行分配。下面通過一段代碼進行講解,對象不一定在堆上進行分配

public class Test {

    public static void test() {
        A a = new A();
    }

    public static void main(String[] args) {
        test();
    }
}

class A {
    int a = 0;
}

像這段代碼,執行test()方法時,new了一個A對象,但是A對象又只有一個基本類型變量a,A對象也沒有出現線程逃逸現象,因為JVM存在一些指令優化功能,並會把A對象直接進行標量替換成int a = 0,這時就會出現對象在棧上進行分配。

堆也是GC進行回收最主要的區域,容易出現OutOfMemoryError異常

​ 堆中又分為young(新生代),old(老年代),分配的內存比例1:2。young新生代中的對象具有"朝生夕死"的特點,也就是對象剛創建不久,並會被回收掉。是Minor回收的主要區域。而老年代中的對象具有的特點則是不容易被回收掉,一些大對象比如數組並會存放在老年代中。是Full GC回收的主要區域。后面寫一篇JVM GC垃圾回收的文章做介紹,這里不做過多的講解了

​ young新生代中又分為eden區,兩個survivor區,分配的內存比例8:1:1。我們程序中所new出的對象首先會在eden區進行分配,然后經過Minor GC,eden區沒有被回收的對象又會被放到survivor Form區,survivor Form區經過GC后的對象沒有被回收掉,又會轉移到survivor To區,這兩個區的對象是可以雙向轉移的。新生代采用GC的算法是復制算法,兩個survivor區正是復制算法所要預留出來的區域

​ 這里主要講解的是JVM內存模型中每個部分所起到作用,其實里面還牽涉到了很多知識,像GC,收集器,類加載等很多知識,后面會一一寫文章進行講解。

初次寫博客,如有寫的不對的地方,還望大家指正!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM