你還在看《深入理解Java虛擬機》的運行時數據模型嗎?
阿新 • • 發佈:2019-04-01
特點 kit 需要 由於 ron amp valueof hotspot 轉移
學習JVM必看的書籍無疑是《深入理解Java虛擬機》這本書了,在書中,關於運行時數據區域模型是這樣描述的:
在這裏我們只針對HotSpot VM來說,它是OracleJDK和OpenJDK中所帶的虛擬機,也是目前使用範圍最廣的Java虛擬機。在JDK7之前,這樣的模型是正確的。但是到了JDK8,如圖標紅的部分,做了一些優化。
什麽是方法區,什麽是永久代,運行時常量池又是什麽
- “方法區”(Method Area),是線程共享的區域,用於存儲已被虛擬機加載的類信息,常量,靜態變量等數據。首先我們要知道,方法區是JVM的一種規範,是一個概念,而這個方法區的具體實現由各個虛擬機廠商去實現。
- “永久代”(Permanent Generation)就是HotSpot虛擬機對於方法區的實現,也僅僅是針HotSpot才有的。
- “運行時常量池”是方法區的一部分。用於存放編譯期生成的各種字面量和符號引用。其特性是具備動態性。
優化一:字符串常量池從永久代劃到Java堆
由於常量池具備動態性,在程序運行過程中會有大量的字符串常量在運行時常量池裏產生,此時如果放在永久代,則無法恰當的設定永久代的大小,容易出現性能問題和內存溢出。下面一個例子證明在JDK8中,字符串常量池已經放在堆中:
String.intern()方法的作用是返回一個字符串引用,引用的是字符串常量池中的字符串(字面量),我們先來驗證一下這個方法:
public class StringConstantsPoolTest { public static void main(String[] args) { String str = "abc"; // str存儲在常量池 String str2 = new String("abc"); // str2 存儲在堆中 System.out.println(str == str2); // 結果為false ,堆中的引用並不等於常量池中的引用 str2 = str2.intern(); // 獲取str2在常量池中的引用 System.out.println(str == str2); } }
結果如下:
證明 String.intern()方法返回了一個在常量池中的引用。
下面驗證字符串常量池在堆中:
設置JVM參數:
-Xms10m -Xmx10m -XX:-UseGCOverheadLimit
public static void main(String[] args) { List<String> list = new ArrayList(); int i = 0; while(true){ list.add(String.valueOf(i++).intern()); } }
結果如下:
我們看到這時報的是Java堆空間內存溢出,說明字符串常量池是在堆中,註意,此時僅僅是字符串常量池轉移到了堆中,但是運行時常量池依舊還是在方法區裏
優化二:移除了永久代,引入“元空間”(Metaspace)
為什麽移除永久代?
- 方法區大小難以設定,容易發生內存溢出 。永久代會存放Class的相關信息,一般這些信息在編譯期間就能確定大小。但是如果是在一些需要動態生成大量Class的應用中,如:Spring的動態代理、大量的JSP頁面或動態生成JSP頁面等,由於方法區的大小在一開始就要分配好,因此就能難確定大小,容易出現內存溢出
- GC復雜且效率低 。方法區存儲了類的元數據信息和各種常量,它的內存回收目標理應當是對這些類型的卸載和常量的回收。但由於這些數據被類的實例引用,卸載條件變得復雜且嚴格,回收不當會導致堆中的類實例失去元數據信息和常量信息。因此,回收方法區內存不是一件簡單高效的事情。
- 促進HotSpot JVM與JRockit VM的融合 。JRockit沒有方法區,移除永久代可以促進HotSpot JVM與JRockit VM的融合。
什麽是元空間(Metaspace),為什麽引入元空間
元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地 內存限制 。
元空間的特點:
- 每個加載器有專門的存儲空間。
- 不會單獨回收某個類。
- 元空間裏的對象的位置是固定的。
- 如果發現某個加載器不再存活了,會把相關的空間整個回收。
總結
最終JVM(HotSpot)運行時數據區域模型如下:
你還在看《深入理解Java虛擬機》的運行時數據模型嗎?