被废弃的持久代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 |