oom 由 metaspace 耗尽引发,表现为 outofmemoryerror: metaspace 和 metaspace 使用量持续锯齿上升;需用 jstat -class、jmap -clstats 定位异常类加载器,并通过 mat 分析 gc roots 确认泄漏源。

排查 Java 中因类加载过多导致的 OOM,关键不是看堆里有多少对象,而是看“谁在反复加载类、谁没卸载、哪些类根本不需要存在”。这类问题最常表现为 java.lang.OutOfMemoryError: Metaspace,尤其在使用 Spring、MyBatis、CGLIB、热部署、OSGi 或自定义类加载器的场景中。
确认是不是 Metaspace 类加载问题
先看错误日志关键词,不是 “Java heap space”,而是:
- OutOfMemoryError: Metaspace
- 或 JVM 日志中频繁出现 Full GC (Metadata GC Threshold)
- 监控图表显示 Metaspace 使用量持续单边上涨,每次 GC 后只回落一点点,整体呈锯齿形爬升
快速定位类加载数量暴增
不重启,用 JDK 自带命令抓现场:
-
jstat -gc
:查看 Metaspace 已使用容量(MU 列)和已加载类数(MC 列)。如果 MC 持续增长且不下降,说明类在不断加载但没卸载 -
jstat -class
:直接输出已加载/已卸载/当前加载类总数。重点关注 loaded 和 unloaded 的差值是否越拉越大 -
jmap -clstats
:列出所有类加载器及其加载的类数量。重点找数量异常高、或类型为 WebAppClassLoader / LaunchedURLClassLoader / 自定义类加载器的条目
查清是谁在动态生成或重复加载类
常见源头有三类:
- 框架代理泛滥:Spring AOP + CGLIB 为每个被代理 Bean 创建新类;MyBatis Mapper 接口动态代理;大量 @Configuration 类触发 ConfigurationClassPostProcessor 反复解析
- 热部署/开发模式残留:DevTools、JRebel、IDE 自动编译导致旧类加载器未回收,新类不断加载;Tomcat 热部署时 WebAppClassLoader 频繁重建
- 自定义类加载器泄漏:未将类加载器设为弱引用、持有静态引用、线程上下文类加载器(TCCL)未重置、加载后未显式调用 close()
验证类加载器是否泄漏
用 jmap -histo:live
- 是否存在多个 WebAppClassLoader 实例?正常应只有 1 个活跃的
- 某个类加载器下是否关联了成百上千个 Class 对象?说明它加载了大量类却没释放
- 右键某个可疑类加载器 → Merge Shortest Paths to GC Roots,看谁在强引用它(比如静态 Map、ThreadLocal、未清理的线程)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











