元空间溢出主因是动态代理类无法卸载而非数量过多;需通过jstat、jcmd、jmap、jstack等工具定位持续生成源与强引用滞留点,并禁用CGLIB无缓存创建、启用类卸载、优先JDK代理等手段根治。

元空间溢出不是类“太多”,而是类“卸不掉”——动态代理类一旦生成,只要加载它的类加载器还活着、Class对象还有强引用,对应的元数据就永远留在本地内存里。排查重点不在数量统计,而在定位谁在持续生成、谁在阻止卸载。
确认动态代理类是否真在撑爆元空间
先验证,再行动:
- 执行 jstat -gc <pid>,观察 MU(Metaspace Used)单边上涨、MGCC(Metaspace GC 次数)长期为 0 或极低 → 表明元空间几乎没回收,高度怀疑类卸载失败
- 运行 jcmd <pid> VM.classloader_stats,若 loadedClassCount 持续上升、unloadedClassCount 几乎不动 → 类加载器泄漏坐实
- 用 jmap -histo:live <pid> | grep -E "(Proxy|Enhancer|CGLIB)" 快速筛选:出现大量
com.sun.proxy.$Proxy\d+(JDK代理)或ClassName$$EnhancerByCGLIB$$[a-f0-9]{8}(CGLIB)即为高风险信号
定位代理类的生成源头
不能只看类名,要结合调用链和上下文:
- 对高频类名执行 jstack <pid> | grep -A 10 -B 5 "EnhancerByCGLIB\|Proxy",找栈中反复出现的方法,例如:
org.springframework.aop.framework.CglibAopProxy.getProxy → Spring 强制 CGLIB 代理
net.sf.cglib.proxy.Enhancer.create → 手动调用 CGLIB 创建代理 - 用 Arthas trace java.lang.ClassLoader.loadClass 实时捕获类加载行为,精准定位哪段业务代码在循环调用
enhancer.create()或Proxy.newProxyInstance() - 检查高危场景:
Spring Boot 单元测试中反复 new AnnotationConfigApplicationContext()
AOP 切面表达式过宽(如@Within(*))导致为第三方类批量生成代理
MyBatis Mapper 接口方法调用频繁且未复用 SqlSession
验证并切断代理类生成与滞留链条
从机制上阻断持续生成和无法卸载:
立即学习“Java免费学习笔记(深入)”;
- 临时加参数 -Dsun.reflect.noInflation=true(禁用反射膨胀)+ -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloading(启用类卸载),压测观察 MU 是否趋稳
- Spring AOP 中显式配置 @EnableAspectJAutoProxy(proxyTargetClass = false),强制走 JDK 接口代理(仅生成少量 $ProxyN 类),避免 CGLIB 为每个类建独立子类
- 检查 CGLIB 使用点:若手动使用 Enhancer,务必开启缓存(
enhancer.setUseCache(true)),禁用缓存(setUseCache(false))是典型 OOM 诱因 - Tomcat 或 Spring Boot DevTools 环境下,确认旧 WebAppClassLoader 是否被线程、静态集合、ThreadLocal 持有 → 用 MAT 分析堆转储,Path to GC Roots 排查强引用链
问题往往藏在“能用就行”的默认配置和未清理的测试逻辑里,盯住类加载器生命周期,比调大 -XX:MaxMetaspaceSize 更治本。


















