确认是outofmemoryerror: metaspace而非堆溢出或系统oom,是排查第一步;说明类加载失控,非内存配置小,而是类堆积未卸载,需通过gc日志、jstat(盯mu/mc)、jcmd(查class模块占用)及arthas定位classloader泄漏源头。

确认是 java.lang.OutOfMemoryError: Metaspace 而不是堆溢出或系统 OOM,是排查的第一步。真出了这个错,说明类加载失控,不是内存配小了,而是类在堆着不走。
看日志和 GC 日志确认是不是 Metaspace 真爆了
别信监控图表,直接翻日志:
- 搜
java.lang.OutOfMemoryError: Metaspace—— 必须精确匹配,不是Java heap space或Direct buffer memory - 查 GC 日志里有没有
Metaspace used行,比如Metaspace used 480240K, capacity 481536K, committed 482304K;如果used接近甚至超过你设的-XX:MaxMetaspaceSize,就是它 - 留意有没有高频
Full GC (Metadata GC Threshold)记录 —— 这表示 JVM 在反复尝试卸载类但失败了
用 jstat 实时盯住 Metaspace 使用趋势
jstat -gc <pid></pid> 是最轻量、最可靠的实时手段,重点关注三列:
-
MU(Metaspace Used):当前已用大小,持续上涨且不回落 → 类加载没停 -
MC(Metaspace Capacity):当前容量上限;若MC缓慢爬升且没设-XX:MaxMetaspaceSize,说明在无节制扩容,迟早吃光系统内存 -
MU/MC比值长期 > 90% → 回收失效,得查谁在 hold 类
用 jcmd 查 Native Memory 和类加载快照
Metaspace 是 native 内存,jmap 和堆 dump 帮不上忙,得用 jcmd:
-
jcmd <pid> VM.native_memory summary scale=MB</pid>→ 看输出中Class模块占用是否异常高(如 > 300MB),这是最直接的 Metaspace 占用证据 -
jcmd <pid> VM.class_hierarchy</pid>或jcmd <pid> VM.native_memory detail scale=MB | grep -A 10 "Class"</pid>→ 定位哪个ClassLoader实例加载了上千个类(典型如WebAppClassLoader、RestartClassLoader) - 临时加参数重启:
-XX:+TraceClassLoading -XX:+TraceClassUnloading,重现场景后grep "Loaded" trace.log | wc -l统计加载量,再grep "Unloaded"看卸载数 —— 如果 Loaded 2w,Unloaded 只有 3 个,问题就非常明确了
用 Arthas 快速定位动态类生成源头
生产环境不能重启、不能加大量启动参数时,arthas-boot.jar 是唯一能无侵入探查类加载行为的工具:
- 连上进程:
java -jar arthas-boot.jar <pid></pid> - 执行
vmtool --action getInstances --className java.lang.ClassLoader --limit 10→ 列出活跃 ClassLoader - 对可疑 ClassLoader 执行
vmtool --action getInstances --className java.lang.Class --classLoaderClass org.springframework.boot.devtools.restart.classloader.RestartClassLoader --limit 50→ 看它到底加载了哪些类 - 重点盯
$EnhancerBySpringCGLIB$、$Proxy、ScriptXXX这类命名模式 —— 它们大概率是循环创建代理或脚本实例导致的
真正难的不是发现类多,而是判断“该不该卸”。很多泄漏藏在类加载器引用链里,比如 Spring Context 没 close、GroovyShell 没缓存、热部署没清理旧 ClassLoader。这些不会在 jstat 里报错,但会让 MU 一路涨到顶。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











