元空间不足可提前从gc日志识别:一看“metadata gc threshold”高频触发,二看“full gc (metadata gc threshold)”后used值几乎不变,三看“class unloading”后used下降不足100kb或classes unloaded长期为0,结合jstat确认gc类型是否支持类卸载,并关联业务操作定位动态类生成源头。

看到 GC 日志里出现 “Metaspace” 字样且伴随特定模式,基本就能断定是元空间不足在作祟——不用等 OOM,线索就藏在日志细节里。
盯住三类关键触发描述
开启 -Xlog:gc*:file=gc.log(JDK 11+)或 -XX:+PrintGCDetails(JDK 8)后,重点关注以下三类高频共现的文本:
- “Metadata GC Threshold”:表示元空间已触达扩容阈值,JVM 主动发起 GC 尝试回收类。每分钟出现多次,说明新类加载速度远超卸载能力
-
“Full GC (Metadata GC Threshold)”:元空间不足直接触发了 Full GC,但回收效果极差——比如日志中显示
Metaspace: 245120K->245088K(249856K),前后几乎没变化 -
“Class unloading” 后无实质释放:哪怕有 “unloaded 12 classes”,但紧接着的
Metaspace used值下降不到 100KB,甚至为 0,说明类加载器还活着,代理类根本卸不掉
结合数字看回收实效
光有触发词不够,得看数字是否“打假”:
- 搜索
Classes unloaded:行,长期为 0 或个位数 → 类卸载机制失效 - 对比每次 GC 前后的
Metaspace used值,回落幅度 持续低于 1% → 元数据堆积严重 - 发现
ClassLoader数量逐次上升(尤其非AppClassLoader的自定义加载器)→ 很可能在反复 new ClassLoader 加载动态代理
顺带确认 GC 类型是否支持类卸载
不是所有 GC 都能清理 Metaspace:
- 用
jstat -gc <pid></pid>查 GC 列,若显示G1或CMS,才可能生效;Parallel GC默认完全不卸载类,加了-XX:+ClassUnloading也无效 - 日志中若出现
[GC (Metadata GC Threshold)]却没跟Class unloading,大概率是 GC 类型不支持,或没配对参数(如 G1 需-XX:+UnlockExperimentalVMOptions -XX:+ClassUnloading)
关联业务时间点快速缩小范围
把 GC 日志时间戳和业务操作对齐:
- 某次上线后,“Metadata GC Threshold” 频率陡增 → 检查是否新增全局 AOP 切面、全包扫描代理、或集成脚本引擎
- 批量任务执行期间集中爆发 → 查该逻辑是否每次调用都 new CGLIBEnhancer 或 Javassist,且未复用生成的类
- 单元测试运行时高频出现 → 基本锁定
@SpringBootTest缺少@DirtiesContext,导致每个测试新建 ApplicationContext 和全套代理类










