方法区溢出典型表现为java.lang.OutOfMemoryError: Metaspace(JDK 8+)或PermGen space(JDK 7及以前),堆内存正常、线程数无异常,多由Fastjson反序列化、CGLIB动态代理、Spring AOP增强或自定义ClassLoader持续加载类引发。

方法区溢出的典型表现
当 JVM 报出 java.lang.OutOfMemoryError: Metaspace(JDK 8+)或 PermGen space(JDK 7 及以前),且堆内存使用正常、线程数无异常时,基本可锁定为方法区(元数据空间)溢出。这类问题往往不发生在启动阶段,而是在运行中持续加载新类后逐步显现——比如高频调用 Fastjson 反序列化、CGLIB 动态代理、Spring AOP 增强、或自定义 ClassLoader 不断 defineClass。
Fastjson 引发元数据膨胀的关键路径
Fastjson 默认开启 Feature.SupportAutoType(尤其旧版本 1.2.24–1.2.80)时,会根据 JSON 字段名动态查找并加载类。若传入恶意或不可控的类名(如 @type:"com.sun.rowset.JdbcRowSetImpl"),不仅触发反序列化漏洞,还会强制 JVM 加载该类及其依赖类到 Metaspace。更隐蔽的是:即使未触发漏洞,只要 JSON 中存在大量不同类型的嵌套对象(如配置项中含数百个自定义 DTO),Fastjson 在解析时会缓存类型信息、生成临时解析器类,反复调用可能造成 Metaspace 持续增长。
- 检查日志中是否频繁出现
autoType is not support或checkAutoType相关 warn - 用
jstat -gc pid观察MU(Metaspace Used)和MC(Metaspace Capacity)是否单向上升 - 禁用 autoType 后观察 Metaspace 是否趋于稳定(
ParserConfig.getGlobalInstance().setAutoTypeSupport(false))
CGLIB 动态类爆炸的识别与收敛
CGLIB 通过字节码增强生成子类(如 UserService$$EnhancerByCGLIB$$a1b2c3d4),每个唯一签名都会产生一个新类。常见失控场景包括:
- 循环中对不同对象反复创建代理(如每次 RPC 调用都 new ProxyFactory)
- 使用了非单例的
Enhancer实例且未设置setUseCache(true) - 代理目标类本身是动态生成的(如 MyBatis Mapper 接口代理再被 CGLIB 二次代理)
验证方式:jcmd pid VM.native_memory summary scale=MB 查看 Class 类别占用;或用 jmap -clstats pid 统计已加载类数量及加载器分布。若发现某 ClassLoader 下类数量超万且持续增加,大概率是 CGLIB 未复用导致。
实战排查四步法
不依赖 dump 文件,快速定位元数据问题:
-
Step 1:加 JVM 参数启动
-XX:NativeMemoryTracking=detail -XX:+PrintGCDetails -
Step 2:服务运行一段时间后执行
jcmd pid VM.native_memory detail | grep -A 10 "Class",确认 Metaspace 提交量是否接近-XX:MaxMetaspaceSize -
Step 3:用
jmap -histo:live pid | head -20看是否有大量形如xxx$$EnhancerByCGLIB$$或com.alibaba.fastjson.parser.DefaultJSONParser$的类 -
Step 4:结合 Arthas
sc -d *CGLIB*或jad反编译可疑类,确认其生成逻辑是否可控、是否遗漏 cache 配置
修复核心原则:控制类生成频次、复用已有代理、显式限制 Metaspace 上限(-XX:MaxMetaspaceSize=256m),并监控 java_lang_ClassLoading_LoadedClassCount JMX 指标。











