OutOfMemoryError: Metaspace 排查:分析类元数据区域异常与动态代理类生成过多的关联

冰川箭仙

冰川箭仙

2026-05-27

958人浏览

原创

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

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$AppClassLoaderorg.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 中避免在循环里手动 new ProxyFactoryBean
  • 避免匿名类触发代理爆炸:不要写 new SomeInterface() { ... } 再去代理它;改为定义命名类,显式传入 SomeImpl.class
  • 让上下文可卸载:单元测试用 @DirtiesContext 替代手动创建/销毁 ApplicationContext;生产环境禁用 DevTools 热重启
  • 检查第三方组件:MyBatis 的 mapper-locations 是否配置重复扫描;Groovy 脚本是否每次请求都重新编译;OSGi Bundle 是否未正确 stop

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

1376

3

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

2023.07.25

1563

9

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

2023.08.02

692

5

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.09

551

4

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

2023.09.05

910

5

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

1128

7

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

2023.09.20

1685

8

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

2023.09.22

5702

6

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

420

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.4万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 131.8万人学习