上段时间有台机器发生了 java.lang.OutOfMemoryError: PermGen space 内存溢出的异常,当时大概判断了原因后就把 MaxPermSize 配置调高后,就把问题解决了,不过空下时间后还是需要继续把review代码。
一般来说PermSize Space OOM的话,第一种可能就是方法区溢出,第二种就是运行时常量池溢出,第二种查看后基本排除掉,问题就应该出现在方法区的溢出,方法区用于存放class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等等,对于这个区域的溢出,基本上都是运行时产生大量的类填满了整个方法区,直到溢出。
spring aop中都是使用到了cglib这类字节码的技术,动态代理的类越多,就需要越多的方法区来保证动态生成的class可以加载入到内存中去,不过spring框架导致的不会因为这种原因。撑爆perm的应该是各种methodaccessorX和constructoracccessorX等等。本来这些accessor也有缓存,但它们使用内存大小敏感的reference引用着的,且使用的是堆内存。当你堆内存吃紧的时候,这些缓存就摧毁了,就必然会不断产生新的methodAccessor字节码,是这个撑爆了perm。所以除增大permsize还应该看看平时运行时堆内存是不是经常用光。下面的例使用cglib直接进行动态代理产生大量的动态类,然后使用jconsole进行观察。
首先将本机的jvm配置为 -XX:PermSize=64M -XX:MaxPermSize=64M ,给到PermSize最大为64M的内存
public class PermgenOOM {
public static void main(String[] args) throws InterruptedException {
int i=0;
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Product.class);
enhancer.setUseCache(false);// 关闭CGLib缓存,否则总是生成同一个类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodproxy) throws Throwable {
// TODO Auto-generated method stub
return methodproxy.invokeSuper(obj,args);
}
});
enhancer.create();
Thread.sleep(100);
}
}
}
很快,系统就抛出了 java.lang.OutOfMemoryError: PermGen space
内存池peimgen的情况
加载类的情况
并且在方法区中,一个类如果要被垃圾收集器回收掉,判断的条件是非常苛刻的,很多人都把方法区称为“永久区”(Permanent Generation),但据说2者在本质上是不一致的,另外还有称呼为“非堆区”,不纠结这个了。
再看看enhancer.setUseCache(false),如果选择为true的话,那么就使用和更新一类具有相同属性生成的类的静态缓存,而不会在同一个类文件还继续被动态加载并视为不同的类,这个其实跟类的equals()和hashCode()有关,它们是与cglib内部的class cache的key相关的。
将上面的程序 enhancer.setUseCache(false) 改为 enhancer.setUseCache(ture)
public class PermgenOOM {
public static void main(String[] args) throws InterruptedException {
int i=0;
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Product.class);
enhancer.setUseCache(true);// 或者不写,默认值就是true
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodproxy) throws Throwable {
// TODO Auto-generated method stub
return methodproxy.invokeSuper(obj,args);
}
});
enhancer.create();
Thread.sleep(100);
}
}
}
内存池peimgen的情况
加载类的情况
可以发现内存池peimgen和加载类的情况并没有呈现直线上涨,已经他们一直都使用者动态类生成类的静态缓存,但是这种动态创建类使用静态缓存在一些情况下并不适合需求。