metaspace oom的本质是类元数据持续增长且无法释放,而非物理内存不足;它使用本地内存,不受-xmx控制,当元数据占用超过-xx:maxmetaspacesize时即抛出outofmemoryerror: metaspace。

Metaspace OOM 的本质不是内存不够,而是类元数据持续增长且无法释放。它和堆内存无关,因为 Metaspace 使用的是本地内存(native memory),不受 -Xmx 控制;一旦加载的类结构(如方法签名、常量池、注解)超出 -XX:MaxMetaspaceSize 限制,JVM 就会直接抛出 OutOfMemoryError: Metaspace 并终止线程或进程。
确认是否真是 Metaspace 耗尽
别靠猜测,用工具看真实占用:
- 运行时执行
jstat -gc <pid></pid>,重点关注 MU(Metaspace used) 和 MC(Metaspace capacity) 是否持续上涨,且 MGCC(Metaspace GC count) 几乎为 0 - 加启动参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,GC 日志中出现类似Metaspace used 245760K, capacity 246144K的行,若used无限逼近甚至超过MaxMetaspaceSize,就是它了 - 配合
-XX:+TraceClassLoading -XX:+TraceClassUnloading,观察日志里是否大量出现Loaded [com.sun.proxy.$Proxyxxx]或net.sf.cglib.proxy.$EnhancerBySpringCGLIB$,但几乎没Unloaded记录
为什么动态代理类特别容易引发 Metaspace OOM
每个动态代理类(比如 Spring AOP、MyBatis Mapper、Feign Client 底层)都会在 Metaspace 中注册完整元数据。关键点在于:
- Class 对象在堆里,元数据在 Metaspace——GC 可以回收堆里的 Class 实例,但无法清理 Metaspace 中对应的结构,除非整个 ClassLoader 被卸载
-
代理类默认不复用:每次调用
Proxy.newProxyInstance()或 CGLIB 的enhancer.create(),只要类名不同(哪怕逻辑一样),就生成一份新元数据 -
ClassLoader 不卸载 = 元数据永远驻留:Spring Boot 测试中反复 new
AnnotationConfigApplicationContext、Tomcat 热部署未清理旧 WebappClassLoader、DevTools 频繁刷新上下文,都会导致代理类“越积越多”
定位泄漏源头的实操步骤
从现象到代码,分三步缩小范围:
- 用
jcmd <pid> VM.classloader_stats</pid>查看各 ClassLoader 加载的类数量,重点关注sun.misc.Launcher$AppClassLoader或org.springframework.boot.loader.LaunchedURLClassLoader是否异常高 - 导出类层次:
jcmd <pid> VM.class_hierarchy | grep -i "proxy\|enhancer\|bytebuddy"</pid>,快速识别高频代理类前缀 - 分析 Heap Dump(如有):用 Eclipse MAT 打开,按
java.lang.ClassLoader分组,看哪个加载器的 retained heap 最大,并展开其 loaded classes,找重复命名模式(如$Proxy123、$EnhancerBySpringCGLIB$abc123)
治本的修复方向与典型改法
加大 MaxMetaspaceSize 只是掩盖问题,真正要控制元数据增长:
-
复用代理类:CGLIB 启用缓存(
enhancer.setUseCache(true),默认开启但需确保 ClassLoader 一致);Spring 中避免在循环里手动 newProxyFactoryBean -
避免匿名类触发代理爆炸:不要写
new SomeInterface() { ... }再去代理它;改为定义命名类,显式传入SomeImpl.class -
让上下文可卸载:单元测试用
@DirtiesContext替代手动创建/销毁ApplicationContext;生产环境禁用 DevTools 热重启 -
检查第三方组件:MyBatis 的
mapper-locations是否配置重复扫描;Groovy 脚本是否每次请求都重新编译;OSGi Bundle 是否未正确 stop











