首頁 >Java >java教程 >JVM記憶體模型與運行時資料區域的詳解(圖文)

JVM記憶體模型與運行時資料區域的詳解(圖文)

不言
不言轉載
2018-10-16 17:02:123659瀏覽

這篇文章帶給大家的內容是關於JVM記憶體模型與執行時間資料區域的詳解(圖文),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

一、java記憶體模型

JVM記憶體模型與運行時資料區域的詳解(圖文)

#java定義記憶體模型的目的是:為了屏蔽各種硬體和作業系統的記憶體存取之間的差異。

java記憶體模型規定了所有的變數都儲存在主記憶體中,每個執行緒擁有自己的工作內存,工作記憶體保存了主記憶體中變數的副本。

執行緒對變數操作只能在工作記憶體中進行,不能直接讀寫主記憶體的變數。

不同執行緒之間的變數存取需要透過主記憶體來完成。

1、java記憶體模型和java運行時資料區的關係:主記憶體對應java堆,工作記憶體對應java棧。

2、volatile關鍵字,使得變數的更新在各個工作記憶體中都是即時可見的。在DCL的單例模式中有運用到

二、java運行時資料區域/記憶體區域

##因為jvm的運行時資料區域一直在改善,所以不同jdk版本之間會有不同。

1、jdk1.7之前的jvm記憶體區域,擁有永久代

JVM記憶體模型與運行時資料區域的詳解(圖文)

1、程序計數器的作用,因為.java文件被編譯成.class文件,它作為當前執行緒所執行的字節碼的行號指示器。當字節碼解釋器運作時,就是透過改變這個計算器的值來選取下一條要執行的字節碼指令。每個線程都有一個獨立的程式計數器。

2、本地方法堆疊就是執行本地native方法的棧,native方法由虛擬機器實作!

3、java虛擬機器堆疊描述的是該執行緒執行java方法(method)時的記憶體模型。每一個方法都對應一個棧幀,棧幀中的局部變數表儲存了方法中的基本資料型別變數、物件參考變數。

JVM記憶體模型與運行時資料區域的詳解(圖文)

如上圖所示,局部變數表保存了方法中宣告的8種基本型別變數和物件參考變數。每一個堆疊幀中還有一個指向運行時常數池的引用,這是指String類型。

下面有一個經典的String物件產生的面試題!

4、java堆是JVM中記憶體最大的一塊,被所有執行緒共享。幾乎所有的物件實例都在這裡分配,

所以java堆也是JVM垃圾回收的主要區域。 java堆又被分成了年輕代,老年代;年輕代進一步可以劃分為Eden空間,From Survivor空間、To Survivor空間。

JVM記憶體模型與運行時資料區域的詳解(圖文)

當我們使用new關鍵字分配物件時,就是在java堆中產生物件。

下面分析一下物件生成時的情況。

  1. 因為Eden最大,所以新產生的物件都被分配到Eden空間,當Eden空間快滿時,進行一次Minor GC,然後將存活的物件複製到From Survivor空間。這時,Eden空間繼續向外提供堆記憶體。

  2. 後面繼續生成的物件還是放到Eden空間,當Eden空間又要滿的時候,這時候Eden空間和From Survivor空間同時進行一次Minor GC,然後把存活對象放到To Survivor空間。這時,Eden空間繼續向外提供堆記憶體。

  3. 接下來的情況和2一致。 Eden空間快滿的時候,Eden空間和To Survivor空間進行一次Minor GC,然後存活的物件放到From Survivor空間。

  4. 接下來的情況和3一致。 Eden空間快慢的時候,Eden空間和From Survivor空間進行一次Minor GC,然後存活的物件放到To Survivor空間。

  5. 就是說2個Survivor中的一個用來提供物件保存。 當Eden空間和某一塊Survivor空間GC後,另一塊Survivor空間放不下GC後存活的對象;或者是連續Minor GC15次左右的情況;就把這部分存活對象放入到老年代空間。

  6. 當老年代空間也放滿的時候,進行Major GC,將舊年代空間回收。 (

    也叫做Full GC,Full GC的記憶體消耗量很大,應該避免)

年輕代使用的是複製演算法:每次Minor GC把Eden區和一塊Survivor區的存活物件複製到另一塊Survivor區。老年代使用的是標記-整理演算法:每次Major GC把存活物件都想記憶體空間的一端移動,然後直接清理掉端邊界以外的記憶體。

大物件如陣列、很長的字串,直接進入老年代空間。

5、方法區用來儲存JVM載入的類別資訊、final常數、static靜態變數等數據,方法區中的資料都是整個程式中唯一的。方法區也包含了執行時間常數池,主要存放編譯期產生的字面量和符號參考(在類別載入後放入)。 String物件的字面量就會被放入到執行時間常數池中。

垃圾回收在方法區主要是對常數的回收和對類型的卸載。

2、jdk1.8及之後的jvm記憶體區域,元空間取代了永久代

JVM記憶體模型與運行時資料區域的詳解(圖文)

# #元空間和永久代的性質是一樣的,都是對JVM方法區的實現,作用是一樣的。不過元空間與永久代之間最大的差別在於:元空間並非在虛擬機器JVM記憶體中,而是使用本地記憶體。

為什麼要用元空間取代永久代呢?

  1. 字串存在永久代中,容易出現效能問題和記憶體溢位。

  2. 類別及方法的資訊等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。

  3. 永久代會為GC帶來不必要的複雜度,且回收效率偏低。

直接記憶體

JDK1.4之後加入的NIO,引入了基於通道channel和緩衝區buffer的IO,直接使用native函數分配堆外內存,顯著提高IO效能,避免了原來BIO的在java堆和naive堆中來回複製資料。

JVM記憶體模型與運行時資料區域的詳解(圖文)

3、字串String產生時的記憶體分配情況

參考文章:

Java中字串常數池的詳解

4、生成物件時的記憶體狀況

下面來分析一下我們常見的生成物件或基本資料型別變數的記憶體模型。這樣可以對JVM有更好的理解。

int i =3;,一個方法對應一個堆疊幀,方法中的基本資料類型變數直接在堆疊幀中分配。如果是static、final類型的基本資料類型則儲存在執行時間常數池中,和String一樣。

Object o1 = new Object();,物件參考(Object o1)儲存在堆疊幀中,但是物件資料(new Object())儲存在java堆中,物件類型資料(Class等資訊)儲存在方法區中。

String s1 = new String("abcd");,使用new聲明的對象,物件參考(String s1)儲存在堆疊幀中,物件資料(new String(“abcd”))儲存在java堆中,字串值(“abcd”)儲存在運行時常數池中。

String s2 = “abc”,物件參考(String s2)儲存在堆疊幀中,字串值(“abc”)儲存在運行時常數池中。

JVM記憶體模型與運行時資料區域的詳解(圖文)

java堆疊、java堆疊、方法區這3者之間的關係大概就是上面的分析所示。

3、各種異常分析

1、java堆記憶體溢出錯誤OutOfMemoryError

如果java堆中分配的物件太多,且GC後記憶體空間還是不夠用。下面透過循環生成物件來消耗記憶體空間進行測試。

相關指令:

VM Args: -Xms20m -Xmx40m,表示JVM分配的堆記憶體最小為20MB,最大為40MB。

 public static void main(String[] args) {
   while (true) {
     List<object> list = new ArrayList(10);
     list.add(new Object());
   }
 }</object>

2、java堆疊堆疊錯誤StackoverflowError

#如果java堆疊的堆疊深度大於JVM允許的深度,就會拋出該錯誤。下面透過無限遞歸呼叫來進行堆疊進行測試。

相關指令:

VM Args: -Xss128k,表示JVM指派的堆疊容量為128KB。

public class StackOOM {
    
    private int length = 1;
    
    public void stackLeak() {
        length++;
        stackLeak();
    }
    
    public static void main(String[] args) {
        StackOOM stackOOM = new StackOOM();
        stackOOM.stackLeak();
    }
}

JVM記憶體模型與運行時資料區域的詳解(圖文)


以上是JVM記憶體模型與運行時資料區域的詳解(圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除