被廢棄的持久代java
想起以前面試的時候有面試官問起過我一個問題:Java 8爲何要廢棄持久代即Metaspace的做用。因爲當時使用的Java 7且研究重心不在JVM上,一下沒有回答上來,今天忽然想起這個問題,就詳細總結一下這個問題。面試
首先咱們看一張JVM內存佈局的圖:jvm
注意到裏面有一塊METHOD AREA,它是一塊線程共享的對象,名爲方法區,在HotSpot虛擬機中,這塊METHOD AREA咱們能夠認爲等同於持久代(PermGen),在Java 6及以前的版本,持久代存放了如下一些內容:佈局
到了Java 7以後,常量池已經不在持久代之中進行分配了,而是移到了堆中,即常量池和對象共享堆內存。spa
接着到了Java 8以後的版本(至此篇文章,Java 10剛發佈),持久代已經被永久移除,取而代之的是Metaspace。.net
爲何要移除持久代線程
HotSpot團隊選擇移除持久代,有內因和外因兩部分,從外因來講,咱們看一下JEP 122的Motivation(動機)部分:3d
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就沒有持久代)。code
從內因來講,持久代大小受到-XX:PermSize和-XX:MaxPermSize兩個參數的限制,而這兩個參數又受到JVM設定的內存大小限制,這就致使在使用中可能會出現持久代內存溢出的問題,所以在Java 8及以後的版本中完全移除了持久代而使用Metaspace來進行替代。對象
Metaspace
上面說了,爲了不出現持久代內存溢出的問題,Java 8及以後的版本完全移除了持久代而使用Metaspace來進行替代。
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 |