元空间溢出本质是类元数据加载失控,需区分“类数量爆炸”与“classloader未卸载”两类根因;通过gc日志(metaspace gc频次、回收率)、jstat -class(loaded/unloaded趋势)及mat分析classloader引用链精确定位,并针对性限制代理缓存或修复类加载器泄漏。

元空间溢出(java.lang.OutOfMemoryError: Metaspace)本身不是笼统的“内存不够”,而是明确指向类元数据加载失控。要精确定位动态类生成引发的性能瓶颈,关键在于区分溢出是源于类数量爆炸,还是ClassLoader未卸载导致元数据堆积——这两类问题的根因、监控指标和修复路径完全不同。
看日志:识别溢出前的元空间增长模式
启动时添加参数:-XX:+PrintGCDetails -Xlog:gc*:file=gc.log:time,重点观察日志中是否频繁出现以下线索:
-
Metaspace GC 触发频繁(如每几分钟就有一条
Metaspace GC记录),说明元空间在反复扩容收缩,类加载压力持续存在 - GC 后 Metaspace 使用率未明显下降(例如使用率从 95% → 92%,而非降到 40% 以下),大概率是 ClassLoader 泄漏,旧类元数据无法回收
-
日志中伴随大量
Loaded类记录(尤其含$$EnhancerByCGLIB、$$ByteBuddy、Proxy$等字样),直接锁定动态代理/字节码增强框架
用 jstat 查类加载趋势:确认是“量大”还是“卸不掉”
执行:jstat -class
- Loaded(已加载类总数)持续单向上升 → 指向“动态类无限生成”,如循环创建 CGLIB 代理、反复编译 Groovy 脚本
- Bytes(元空间占用字节数)增长快但 Unloaded(已卸载类数)几乎为 0 或极低 → 指向“ClassLoader 泄漏”,典型于 Tomcat 热部署未清理上下文、Spring Boot DevTools 未关闭
抓堆转储 + MAT 分析:定位泄漏源头
发生溢出时若已配置 -XX:+HeapDumpOnOutOfMemoryError,会生成 hprof 文件。用 MAT 打开后执行:
- 打开 Leak Suspects Report,查看是否有大量
java.net.URLClassLoader或自定义WebAppClassLoader实例 - 对任一可疑 ClassLoader 右键 → Path to GC Roots → exclude weak/soft references,看哪些静态引用或线程局部变量持有了它
- 筛选 java.lang.Class 对象,按 Package Name 分组,检查是否集中于
com.xxx.generated、net.sf.cglib等动态包名
验证与收口:针对性加固
根据上述诊断结论选择对应动作:
- 若是类数量过多:限制 CGLIB 代理缓存(
System.setProperty("cglib.cache", "false"))、禁用运行时脚本编译、将动态类生成逻辑改为预热+复用 - 若是ClassLoader 卸载失败:Tomcat 中关闭 autoDeploy、Spring Boot 中禁用 DevTools、确保所有线程池在应用关闭前 shutdown 并清除 ThreadLocal
- 无论哪种,都应设置硬上限:-XX:MaxMetaspaceSize=512m,避免耗尽系统本地内存影响其他进程











