Java 8 的 Metaspace
https://www.cnblogs.com/xrq730/p/8688203.html
被廢棄的持久代
想起之前面試的時候有面試官問起過我一個問題:Java 8為什么要廢棄持久代即Metaspace的作用。由於當時使用的Java 7且研究重心不在JVM上,一下沒有回答上來,今天突然想起這個問題,就詳細總結一下這個問題。
JRockit是oracle發明的,用於其WebLogic服務器,IBM JVM是IBM發明的用於其Websphere服務器(所以在某行開發的時候,他們用的是IBM的JDK,因為他們使用的IBM的應用程序服務器Websphere,使用其他JDK可能存在兼容性問題)。
JRockit和J9不存在永久代這種說法。這里只討論HotSpot虛擬機,這也是目前使用的最多的JVM。Sun JDK7 HotSpot虛擬機的內存模型如下圖所示:
注意到里面有一塊METHOD AREA,它是一塊線程共享的對象,名為方法區,在HotSpot虛擬機中,這塊METHOD AREA我們可以認為等同於持久代(PermGen),在Java 6及之前的版本,持久代存放了以下一些內容:
- 虛擬機加載的類信息
- 常量池
- 靜態變量
- 即時編譯后的代碼
到了Java 7之后,常量池已經不在持久代之中進行分配了,而是移到了堆中,即常量池和對象共享堆內存。
接着到了Java 8之后的版本(至此篇文章,Java 10剛發布),持久代已經被永久移除,取而代之的是Metaspace。
方法區和永久代的關系
在Java虛擬機規范中,方法區在虛擬機啟動的時候創建,雖然方法區是堆的邏輯組成部分,但是簡單的虛擬機實現可以選擇不在方法區實現垃圾回收與壓縮。這個版本的虛擬機規范也不限定實現方法區的內存位置和編譯代碼的管理策略。所以不同的JVM廠商,針對自己的JVM可能有不同的方法區實現方式。
在HotSpot中,設計者將方法區納入GC分代收集。HotSpot虛擬機堆內存被分為新生代和老年代,對堆內存進行分代管理,所以HotSpot虛擬機使用者更願意將方法區稱為老年代。
方法區和永久代的關系很像Java中接口和類的關系,類實現了接口,而永久代就是HotSpot虛擬機對虛擬機規范中方法區的一種實現方式。
我們知道在HotSpot虛擬機中存在三種垃圾回收現象,minor GC、major GC和full GC。對新生代進行垃圾回收叫做minor GC,對老年代進行垃圾回收叫做major GC,同時對新生代、老年代和永久代進行垃圾回收叫做full GC。許多major GC是由minor GC觸發的,所以很難將這兩種垃圾回收區分開。major GC和full GC通常是等價的,收集整個GC堆。但因為HotSpot VM發展了這么多年,外界對各種名詞的解讀已經完全混亂了,當有人說“major GC”的時候一定要問清楚他想要指的是上面的full GC還是major GC。
為什么要移除持久代
HotSpot團隊選擇移除持久代,有內因和外因兩部分,從外因來說,我們看一下JEP 122的Motivation(動機)部分:
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
大致就是說移除持久代也是為了和JRockit進行融合而做的努力,JRockit用戶並不需要配置持久代(因為JRockit就沒有持久代)。
從內因來說,持久代大小受到-XX:PermSize和-XX:MaxPermSize兩個參數的限制,而這兩個參數又受到JVM設定的內存大小限制,這就導致在使用中可能會出現持久代內存溢出的問題,因此在Java 8及之后的版本中徹底移除了持久代而使用Metaspace來進行替代。
Metaspace ( 元空間 )
上面說了,為了避免出現持久代內存溢出的問題,Java 8及之后的版本徹底移除了持久代而使用Metaspace來進行替代。類的元信息被存儲在元空間中。元空間沒有使用堆內存,而是與堆不相連的本地內存區域。所以,理論上系統可以使用的內存有多大,元空間就有多大,所以不會出現永久代存在時的內存溢出問題。這項改造也是有必要的,永久代的調優是很困難的,雖然可以設置永久代的大小,但是很難確定一個合適的大小,因為其中的影響因素很多,比如類數量的多少、常量數量的多少等。永久代中的元數據的位置也會隨着一次full GC發生移動,比較消耗虛擬機性能。同時,HotSpot虛擬機的每種類型的垃圾回收器都需要特殊處理永久代中的元數據。將元數據從永久代剝離出來,不僅實現了對元空間的無縫管理,還可以簡化Full GC以及對以后的並發隔離類元數據等方面進行優化。
Metaspace是方法區在HotSpot中的實現,它與持久代最大的區別在於:Metaspace並不在虛擬機內存中而是使用本地內存。因此Metaspace具體大小理論上取決於32位/64位系統可用內存的大小,可見也不是無限制的,需要配置參數。
接着我們模擬一下Metaspace內存溢出的情況,前面說了持久代存放了以下信息:
- 虛擬機加載的類信息
- 常量池
- 靜態變量
- 即時編譯后的代碼
所以最簡單的模擬Metaspace內存溢出,我們只需要無限生成類信息即可,類占據的空間總是會超過Metaspace指定的空間大小的,下面用Cglib來模擬:
1 public class MetaspaceOOMTest { 2 3 /** 4 * JVM參數:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m -XX:+PrintFlagsInitial 5 */ 6 public static void main(String[] args) { 7 int i = 0; 8 9 try { 10 for (;;) { 11 i++; 12 13 Enhancer enhancer = new Enhancer(); 14 enhancer.setSuperclass(OOMObject.class); 15 enhancer.setUseCache(false); 16 enhancer.setCallback(new MethodInterceptor() { 17 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 18 return proxy.invokeSuper(obj, args); 19 } 20 }); 21 enhancer.create(); 22 } 23 } catch (Exception e) { 24 System.out.println("第" + i + "次時發生異常"); 25 e.printStackTrace(); 26 } 27 } 28 29 static class OOMObject { 30 31 } 32 33 }
虛擬機參數設置為"-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m",運行代碼,結果為:
1 第15562次時發生異常 2 net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null 3 at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345) 4 at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492) 5 at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114) 6 at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291) 7 at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480) 8 at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305) 9 at org.xrq.commom.test.jvm.MetaspaceOOMTest.main(MetaspaceOOMTest.java:34) 10 Caused by: java.lang.reflect.InvocationTargetException 11 at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) 12 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) 13 at java.lang.reflect.Method.invoke(Unknown Source) 14 at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:413) 15 at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336) 16 ... 6 more 17 Caused by: java.lang.OutOfMemoryError: Metaspace 18 at java.lang.ClassLoader.defineClass1(Native Method) 19 at java.lang.ClassLoader.defineClass(Unknown Source) 20 ... 11 more
可見即使使用了Metaspace,也是有OOM的風險的,但是由於Metaspace使用本機內存,因此只要不要代碼里面犯太低級的錯誤,OOM的概率基本是不存在的。
Metaspace相關JVM參數
最后我們來看一下Metaspace相關的幾個JVM參數:
參數名 | 作 用 |
MetaspaceSize | 初始化的Metaspace大小,控制Metaspace發生GC的閾值。GC后,動態增加或者降低MetaspaceSize,默認情況下,這個值大小根據不同的平台在12M到20M之間浮動 |
MaxMetaspaceSize | 限制Metaspace增長上限,防止因為某些情況導致Metaspace無限使用本地內存,影響到其他程序,默認為4096M |
MinMetaspaceFreeRatio | 當進行過Metaspace GC之后,會計算當前Metaspace的空閑空間比,如果空閑比小於這個參數,那么虛擬機增長Metaspace的大小,默認為40,即70% |
MaxMetaspaceFreeRatio | 當進行過Metaspace GC之后,會計算當前Metaspace的空閑空間比,如果空閑比大於這個參數,那么虛擬機會釋放部分Metaspace空間,默認為70,即70% |
MaxMetaspaceExpanison | Metaspace增長時的最大幅度,默認值為5M |
MinMetaspaceExpanison | Metaspace增長時的最小幅度,默認為330KB |
============ End