jdk8之永久区Permanent区参数设置分析
引言: JVM中的内存区域一般分为3个部分: 年轻代、年老代和永久代;永久代在JDK 7中逐渐变化,到JDK 8之后完全消失,合并到了Native堆中。本文将逐个分析其中的使用和状况。
1. 环境说明
windows 7, JDK 1.7.0_79/JDK 1.8.0_45. STS
2. 何为永久区?
主要是JVM在运行过程中,存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息。一般很少被JVM进行回收。一般的动态替换Class的行为都是在这个区域来进行的。
3. JDK 7下的永久区参数设置
参数设置示例: -XX:PermSize=5M -XX:MaxPermSize=7M
说明: PermSize为永久区大小, MaxPermSize为最大的永久区大小。
代码示例:
JVM参数: -XX:PermSize=5M -XX:MaxPermSize=7M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\oom.dump
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.CallbackFilter;
- import net.sf.cglib.proxy.Dispatcher;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- public class OOMTest {
- public static void main(String[] args) {
- System.out.println("Let us do it now.....");
- for(int i=0;i<100000;i++){
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(BaseFlyer.class);
- enhancer.setCallbackTypes(new Class[] {
- Dispatcher.class, MethodInterceptor.class });
- enhancer.setCallbackFilter(new CallbackFilter() {
- public int accept(Method method) {
- return 1;
- }
- });
- Class clazz = enhancer.createClass();
- System.out.println("Time:" + System.currentTimeMillis());
- }
- }
- }
- Time:1470203973444
- Time:1470203973447
- Time:1470203973449
- Time:1470203973452
- Time:1470203973456
- Time:1470203973459
- java.lang.OutOfMemoryError: PermGen space
- Dumping heap to d:\oom.dump ...
- Unable to create d:\oom.dump: File exists
- Exception in thread "main"
- Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
基于jvisualvm来打开dump文件,可以发现defineClass的时候发生的内存溢出,另外出现了大量的BaseFlyer子类的动态类,这个导致了Perm区域的溢出。
在defineClass()之时,报出溢出错误信息:
在JDK 7中开始进行移除永久代的努力,下面列出了JDK7中从永久带移除的东西:
- 符号引用被移到了native堆
- 池化string对象被移到了java堆
- Class对象、静态变量被移到了java堆
4. JDK 8中的永久区设置
JDK8中已经完全移除了永久带。这项工作是在这个bug:https://bugs.openjdk.java.net/browse/JDK-6964458推动下完成的。JDK8中,PermSize和MaxPermSize参数也一并移除了。
在移除了Perm区域之后,JDK 8中使用MetaSpace来替代,这些空间都直接在堆上来进行分配。 在JDK8中,类的元数据存放在native堆中,这个空间被叫做:元数据区。JDK8中给元数据区添加了一些新的参数。
- -XX:MetaspaceSize=<NNN> <NNN>是分配给类元数据区(以字节计)的初始大小(初始高水位),超过会导致垃圾收集器卸载类。这个数量是一个估计值。当第一次到达高水位的时候,下一个高水位是由垃圾收集器来管理的。
- -XX:MaxMetaspaceSize=<NNN> <NNN>是分配给类元数据区的最大值(以字节计)。这个参数可以用来限制分配给类元数据区的大小。这个值也是个估计值。默认无上限。
- -XX:MinMetaspaceFreeRatio=<NNN>,<NNN>是一次GC以后,为了避免增加元数据区(高水位)的大小,空闲的类元数据区的容量的最小比例,不够就会导致垃圾回收。
- -XX:MaxMetaspaceFreeRatio=<NNN>,<NNN>是一次GC以后,为了避免减少元数据区(高水位)的大小,空闲的类元数据区的容量的最大比例,超过就会导致垃圾回收。
默认情况下,类元数据的分配仅受限于可用的本地内存。我们可以使用新的MaxMetaspaceSize参数限定类元数据可用的本地内存的数量。它类似于MaxPermSize。当类元数据区使用量到达MetaspaceSize(32位机客户端模式12M,32位服务器模式16M,64位机会更大)的时候,会触发垃圾回收,然后回收掉无用的类加载器和class对象。
MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
代码示例:
JVM参数设置: -XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=7M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\oom.dump
cglib依赖包:
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.2.4</version>
- </dependency>
代码内容:
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.CallbackFilter;
- import net.sf.cglib.proxy.Dispatcher;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- public class OOMTest {
- public static void main(String[] args) {
- System.out.println("Let us do it now.....");
- for(int i=0;i<100000;i++){
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(BaseFlyer.class);
- enhancer.setCallbackTypes(new Class[] {
- Dispatcher.class, MethodInterceptor.class });
- enhancer.setCallbackFilter(new CallbackFilter() {
- public int accept(Method method) {
- return 1;
- }
- });
- Class clazz = enhancer.createClass();
- System.out.println("Time:" + System.currentTimeMillis());
- }
- }
- }
- Time:1470217316712
- Time:1470217316716
- Time:1470217316718
- Time:1470217316721
- Time:1470217316724
- Time:1470217316727
- Time:1470217316730
- java.lang.OutOfMemoryError: Metaspace
- Dumping heap to d:\oom.dump ...
- Heap dump file created [2577942 bytes in 0.022 secs]
- Exception in thread "main" java.lang.IllegalStateException: Unable to load cache item
- at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:79)
- at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
- at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:116)
- at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
- at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
- at net.sf.cglib.proxy.Enhancer.createClass(Enhancer.java:337)
- at org.homework.test.jvm.jvmopt.OOMTest.main(OOMTest.java:25)
- Caused by: java.lang.OutOfMemoryError: Metaspace
- at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
- at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
- at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:93)
- at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:91)
- at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
- at java.util.concurrent.FutureTask.run(Unknown Source)
- at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
- ... 6 more
分析错误日志,发现Metaspace是核心关键词,如果发现类似的关键词,即可确定是Metaspace的设置问题。
基于jvisualvm来进行dump文件分析的结果与JDK 7中的结果类似,这里不再重复赘述,各位可以自行研究分析。
4. G1垃圾回收算法
G1是在JDK 7中推出的新GC算法,相比之前的JVM内存模型,这是一个巨大的飞跃,慢慢打破了不同代系之间的阻隔。 G1和永久区的逐步消失是密切耦合的。
在G1中,堆被划分成 许多个连续的区域(region)。每个区域大小相等,在1M~32M之间。JVM最多支持2000个区域,可推算G1能支持的最大内存为2000*32M=62.5G。区域(region)的大小在JVM初始化的时候决定,也可以用-XX:G1HeapReginSize设置。
在G1中没有物理上的Yong(Eden/Survivor)/Old Generation,它们是逻辑的,使用一些非连续的区域(Region)组成的。
G1虽然保留了CMS关于代的概念,但是代已经不是物理上连续区域,而是一个逻辑的概念。在标记过程中,每个区域的对象活性都被计算,在回收时候,就可以根据用户设置的停顿时间,选择活性较低的区域收集,这样既能保证垃圾回收,又能保证停顿时间,而且也不会降低太多的吞吐量。Remark阶段新算法的运用,以及收集过程中的压缩,都弥补了CMS不足。引用Oracle官网的一句话:“G1 is planned as the long term replacement for the Concurrent Mark-Sweep
Collector (CMS)”。
这里先简要提一下G1的垃圾回收算法,稍后我还会专门描述G1的回收算法。
5. 总结
在这里我们分别在JDk 7和JDK 8针对永久区进行了分析,这里需要着重强调的是两者之间是无法彼此兼容的设置,各自自行设置调整。
http://blog.****.net/blueheart20/article/details/52103020