大量DelegatingClassLoader类加载器,导致Perm区溢出

当使用Java反射时,Java虚拟机有两种方法获取被反射的类的信息。它可以使用一个JNI存取器;如果使用Java字节码存取器,则需要拥有它自己的Java类和类加载器(sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader),这些类和类加载器使用本机内存。字节码存取器也可以被JIT编译,这样会增加本机内存的使用。如果Java反射被频繁使用,会显著地增加本机内存的使用。

Java虚拟机会首先使用JNI存取器,然后在访问了同一个类若干次后,会改为使用Java字节码存取器。这种当Java虚拟机从JNI存取器改为字节码存取器的行为被称为膨胀(Inflation)。Inflation机制提高了反射的性能,但是对于重度使用反射的项目可能存在隐患,它带来了两个问题:(1)初次加载的性能损失;(2)动态加载的字节码导致PermGen持续增长。幸运的是,我们可以通过一个设置-Dsun.reflect.inflationThreshold=N控制这种行为,sun.reflect.inflationThreshold会告诉Java虚拟机使用JNI存取器多少次。如果设为0,则总是使用JNI存取器。

正常情况下,在Java中类是永久存在的。所以一旦类被加载,他们将一直停留在内存中,即便其所在应用服务器端已经停止运行。像cglib这样的动态类产生库,在他们动态创建了大量类之后,会使用大量永久代内存空间。在运行时创建的代理类被广泛地使用,当单个类定义被用于产生多个实例时,创建新的代理类将会很容易。Spring和Hibernate经常产生某个类的代理,这些代理类被类加载器加载,产生的类定义从不被清理,导致永久性堆空间迅速填满。

关掉Inflation会带来一定程度上的性能损失,因此不到万不得已的情况下并不要将其关闭,sun.reflect.inflationThreshold的默认值在不同的实现版本中有不同的值,例如在IBM Developer Kit for Java 5.0 的默认值为15。可以尝试将这个值设置大一点,例如100。虽然Java中的class放在perm区中默认是不被GC的,但是我们可以指定让他也参与GC,通过打开两个参数:-XX+CMSClassUnloadingEnabled -XX+UseConcMarkSweepGC,这样GC的时候在perm区中的垃圾class元数据也会被回收掉,从而释放perm区的内存空间。JDK8已经没有了perm区的概念,类的元数据被存放在Metaspace中,会自动进行垃圾回收,卸载掉不再使用的类。

另外需要注意的是,检查JVM启动参数中是否使用了-Xnoclassgc,如果增加了这个参数,class是不会被GC掉的,需要去掉这个参数。

《大量DelegatingClassLoader类加载器,导致Perm区溢出》上有1条评论

  1. 大量DelegatingClassLoader类加载器,导致Perm区溢出

    你好问下: 如果 某个dao 类 实现xxxDaoImpl;有find 方法,我通过 method.inovke(xxxDaoImpl); 这样的方式一直调用;反射最开始是JNI方式调用 超过15次后 字节码的方式调用;并且也JIT了; 如果调用百次、千次、万次、、、、perm里对于 xxxDaoImpl 的sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader 是不是只有一份?

    如果 xxxDaoImpl 有 十个方法;十个方法都是 method.invoke 的方式调用; 对于xxxDaoImpl 的 sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader 是只有一份,还是 十份?或者还是多份的?
    请教了 多谢~~

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注