元空间OOM本质是类卸载失败与类加载器不释放,需通过jstat、jcmd、jmap、jstack及Arthas定位动态类生成源和ClassLoader泄漏点,禁用反射膨胀、限制CGLIB代理、启用类卸载并修复ClassLoader生命周期管理。

动态类生成过多导致元空间在加载阶段持续吃紧,本质不是“类太多”,而是“类卸载失败+加载器不释放”。排查要绕开堆内存干扰,直击类加载生命周期关键节点。
确认元空间是否真被动态类撑满
先排除误判:堆内存正常但服务 RSS 持续上涨、频繁报 java.lang.OutOfMemoryError: Metaspace,才是典型信号。
- 执行 jstat -gc <pid>,观察 MU(Metaspace used) 和 MC(Metaspace capacity) 是否单向增长,且 MGCC(Metaspace GC 次数) 长期为 0 或极低 → 表明元空间几乎没触发有效回收
- 运行 jcmd <pid> VM.classloader_stats,检查 loadedClassCount 持续上升、unloadedClassCount 几乎不动 → 类卸载卡死,大概率是类加载器泄漏
- 用 jmap -histo:live <pid> | grep -E "(Generated|Enhancer|Proxy|Accessor)" 快速筛出高频动态类名,例如:
net.sf.cglib.proxy.Enhancer$$EnhancerByCGLIB$$[a-f0-9]{8} → CGLIB 代理
com.sun.proxy.$Proxy[0-9]+ → JDK Proxy
sun.reflect.GeneratedMethodAccessor[0-9]+ → 反射膨胀类
定位动态类的源头生成点
不同技术栈生成类的特征和调用链不同,不能只看类名,要结合线程栈和上下文。
- 对疑似类名执行 jstack <pid> | grep -A 10 -B 5 "EnhancerByCGLIB\|GeneratedMethodAccessor",找调用栈中高频出现的方法,如:
org.springframework.aop.framework.CglibAopProxy.getProxy → Spring AOP 强制 CGLIB 代理
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize → Jackson 反序列化触发反射访问 - 用 Arthas trace java.lang.ClassLoader.loadClass 或 watch sun.misc.Unsafe.defineClass,捕获实时类定义行为,精准定位哪段业务代码在反复 new Class
- 检查常见高危场景:
Spring Boot 单元测试中反复 new AnnotationConfigApplicationContext()
MyBatis 执行大量不同 Mapper 接口方法,触发独立代理类生成
Groovy 脚本热编译未关闭缓存,每次变更都生成新 Class
AOP 切面表达式过宽(如 @Within(*)),导致为第三方工具类批量生成代理
验证类卸载机制是否失效
Metaspace 不是“满了就 OOM”,而是“加载器活着,类就赖着不走”。卸载失败才是根本症结。
- 加 JVM 参数 -XX:+TraceClassLoading -XX:+TraceClassUnloading(仅测试环境),观察日志中是否有大量 Loaded 但极少 Unloaded 记录;若出现 Unloading class sun.reflect.GeneratedMethodAccessor123 后又立即 Loaded sun.reflect.GeneratedMethodAccessor124,说明反射类在滚动生成却无法复用
- 执行 jmap -clstats <pid>,重点看 sun.reflect.DelegatingClassLoader 实例数量。若达数百上千个,每个只加载 1–2 个类 → 基本锁定反射膨胀;若出现大量 LaunchedURLClassLoader 或 WebAppClassLoader 未被回收 → 热部署或模块重载泄漏
- 开启 NMT:-XX:NativeMemoryTracking=summary,再运行 jcmd <pid> VM.native_memory summary scale=MB,比对 Metaspace / Class 区用量增长是否与 ClassLoader 数量增长同步 → 若同步,则问题在加载器生命周期管理
针对性缓解与修复动作
扩容只是掩耳盗铃,核心是让类能复用、延迟生成、或加载器可回收。
- 禁用反射膨胀:-Dsun.reflect.noInflation=true → 所有反射调用统一走 MethodHandle 路径,不再生成 GeneratedMethodAccessor
- 限制 CGLIB 代理滥用:@EnableAspectJAutoProxy(proxyTargetClass = false) 强制接口代理;或对非必要类关闭切面,避免 @Within(*) 全局匹配
- 启用类卸载保障:-XX:+ClassUnloadingWithConcurrentMark(G1 默认开启),确保并发标记阶段可触发类卸载
- 修复自定义 ClassLoader:
确保 loadClass 方法不持有外部强引用(如静态 Map、ThreadLocal)
在不用时主动 close() 或置 null,尤其注意 Web 容器中旧 ContextClassLoader 的清理

















