简单分析cglib引起的PermSize Space内存溢出

本文详细分析了Java内存溢出错误的原因,特别是PermGen空间溢出的问题,通过使用CGlib进行动态代理产生的大量类实例导致方法区空间不足,最终引发内存溢出。通过修改配置参数和理解类缓存机制,解决内存溢出问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上段时间有台机器发生了 java.lang.OutOfMemoryError: PermGen space 内存溢出的异常,当时大概判断了原因后就把 MaxPermSize 配置调高后,就把问题解决了,不过空下时间后还是需要继续把review代码。

一般来说PermSize Space OOM的话,第一种可能就是方法区溢出,第二种就是运行时常量池溢出,第二种查看后基本排除掉,问题就应该出现在方法区的溢出,方法区用于存放class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等等,对于这个区域的溢出,基本上都是运行时产生大量的类填满了整个方法区,直到溢出。
spring aop中都是使用到了cglib这类字节码的技术,动态代理的类越多,就需要越多的方法区来保证动态生成的class可以加载入到内存中去,下面的例使用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和加载类的情况并没有呈现直线上涨,已经他们一直都使用者动态类生成类的静态缓存,但是这种动态创建类使用静态缓存在一些情况下并不适合需求。
<think>好的,我现在需要回答用户关于JVM内存模型中方法区的作用与实现的问题。首先,我要回忆一下自己对方法区的理解,然后结合提供的引用资料来组织答案。 首先,方法区是JVM内存模型的一部分,属于线程共享的区域。根据引用[1],方法区在JVM内存模型中列出的第五个部分,主要用于存储类信息、常量、静态变量等。引用[3]提到虚方法表存放在方法区,这说明方法区还包含方法的相关数据,如虚方法表,用于优化动态分派。引用[4]虽然主要讲堆参数,但间接说明方法区与其他内存区域的设置不同,可能涉及不同的内存管理策略。 接下来,方法区的作用可能包括存储类元数据、运行时常量池、静态变量和JIT编译后的代码。需要确认这些内容是否准确,比如运行时常量池在引用[1]中是单独列出的,但可能属于方法区的一部分。根据标准JVM规范,运行时常量池确实是方法区的一部分。 关于实现方面,不同的JVM版本有不同的实现。在JDK8之前,方法区使用永久代(PermGen),但容易导致内存溢出。JDK8之后改用元空间(Metaspace),使用本地内存,减少了对JVM内存的限制,提高了灵活性。引用[4]提到的参数如-XX:MetaspaceSize和-XX:MaxMetaspaceSize可以调整元空间的大小,这说明用户可以通过这些参数来配置方法区。 另外,需要注意方法区与堆的关系。方法区和堆都是线程共享的,但存储的数据类型不同。方法区主要存储类信息,而堆存储对象实例。引用[2]提到JVM内存模型规定了线程如何访问内存区域,这可能涉及到方法区的访问机制,但用户的问题可能更关注结构和作用。 可能的问题点:运行时常量池是否属于方法区?根据引用[1],运行时常量池是第七项,但通常认为它属于方法区的一部分。需要确认这一点,可能引用资料中存在差异,但根据标准规范,运行时常量池是方法区的一部分。 最后,需要确保回答结构清晰,分点说明作用和实现,并引用相关资料。同时,生成的相关问题要围绕方法区的其他方面,如内存溢出、调优、与元空间的关系等。</think>### JVM内存模型中的方法区:作用与实现 #### 一、方法区的作用 方法区(Method Area)是JVM内存模型中**线程共享**的内存区域,主要存储以下内容: 1. **类元数据** 包括类的完整结构信息(如类名、父类、接口、字段、方法等)、方法字节码、访问修饰符等。这些信息在类加载阶段被解析并存储[^1][^3]。 2. **运行时常量池** 存放编译期生成的字面量(如字符串常量)和符号引用。它是方法区的一部分,动态性较强,支持运行时添加新常量。 3. **静态变量(类变量)** 类中通过`static`定义的变量直接存储在方法区,与类的生命周期一致。 4. **JIT编译后的代码** 即时编译器(Just-In-Time Compiler)将热点代码编译为本地机器码后,也可能存储在方法区。 #### 二、方法区的实现 方法区的具体实现因JVM版本而异: 1. **JDK 8之前:永久代(PermGen)** - 使用堆内存的一部分,通过参数`-XX:PermSize`和`-XX:MaxPermSize`调整大小。 - **缺陷**:容易因加载过多类或常量导致`OutOfMemoryError: PermGen space`[^4]。 2. **JDK 8及之后:元空间(Metaspace)** - 改用本地内存(Native Memory),不再受限于JVM堆大小。 - 通过参数`-XX:MetaspaceSize`(初始大小)和`-XX:MaxMetaspaceSize`(最大大小)管理。 - **优势**:自动扩展,减少内存溢出风险,且垃圾回收效率更高。 #### 三、与虚方法表的关系 方法区中维护**虚方法表(Virtual Method Table)**,用于优化动态分派(如多态调用)。每个类在加载时生成虚方法表,存储方法的实际入口地址,避免每次调用时重复搜索方法元数据。 #### 四、典型问题与调优 - **内存溢出**:频繁生成动态类(如反射、CGLib)可能导致元空间溢出,需监控`Metaspace`使用情况。 - **调优建议**:根据应用场景调整`-XX:MaxMetaspaceSize`,并结合工具(如JProfiler、MAT)分析内存快照。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值