转载

JVM之永久区Permanent区参数设置分析

引言

JVM中的内存区域一般分为3个部分: 年轻代、年老代和永久代;永久代在JDK 7中逐渐变化,到JDK 8之后完全消失,合并到了Native堆中。本文将逐个分析其中的使用和状况。

环境说明

windows 7, JDK 1.7.0_79/JDK 1.8.0_45. STS

何为永久区?

主要是JVM在运行过程中,存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息。一般很少被JVM进行回收。一般的动态替换Class的行为都是在这个区域来进行的。

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"

注意这里的关键词为:java.lang.OutOfMemoryError: PermGen space, 永久区溢出。
基于jvisualvm来打开dump文件,可以发现defineClass的时候发生的内存溢出,另外出现了大量的BaseFlyer子类的动态类,这个导致了Perm区域的溢出。

在defineClass()之时,报出溢出错误信息:

在JDK 7中开始进行移除永久代的努力,下面列出了JDK7中从永久带移除的东西:

符号引用被移到了native堆
池化string对象被移到了java堆
Class对象、静态变量被移到了java堆
上述信息都被转移到了Java堆中了。

JDK 8中的永久区设置

JDK8中已经完全移除了永久带。这项工作是在这个bug:https://bugs.openjdk.java.net/browse/JDK-6964458 推动下完成的。JDK8中,PermSize和MaxPermSize参数也一并移除了。

在移除了Perm区域之后,JDK 8中使用MetaSpace来替代,这些空间都直接在堆上来进行分配。 在JDK8中,类的元数据存放在native堆中,这个空间被叫做:元数据区。JDK8中给元数据区添加了一些新的参数。

-XX:MetaspaceSize= 是分配给类元数据区(以字节计)的初始大小(初始高水位),超过会导致垃圾收集器卸载类。这个数量是一个估计值。当第一次到达高水位的时候,下一个高水位是由垃圾收集器来管理的。

-XX:MaxMetaspaceSize= 是分配给类元数据区的最大值(以字节计)。这个参数可以用来限制分配给类元数据区的大小。这个值也是个估计值。默认无上限。

-XX:MinMetaspaceFreeRatio=是一次GC以后,为了避免增加元数据区(高水位)的大小,空闲的类元数据区的容量的最小比例,不够就会导致垃圾回收。

-XX:MaxMetaspaceFreeRatio=是一次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中的结果类似,这里不再重复赘述,各位可以自行研究分析。

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的回收算法。

总结

在这里我们分别在JDk 7和JDK 8针对永久区进行了分析,这里需要着重强调的是两者之间是无法彼此兼容的设置,各自自行设置调整。

正文到此结束
本文目录