确认类加载器是否存活需检查其是否被强引用持有:通过-xx:+traceclassloading/unloading日志观察加载卸载比例,用mat分析堆转储中可疑类加载器的gc roots引用链,重点排查tccl、静态缓存及第三方库缓存导致的引用阻断。

确认类加载器是否真的“活着”
类加载器没被回收,它加载的所有类元数据就一直卡在元空间里不释放。判断它是否存活,不能只看有没有实例,得看有没有外部强引用把它拽住。常见线索包括:热部署后出现多个 WebAppClassLoader、RestartClassLoader 或 DevToolsRestartClassLoader 实例;每个实例的 Loaded Classes 数量持续增长;堆转储中它们的 Retained Heap 达几十 MB 且长期不降。
抓日志,看加载和卸载是否对得上
启动 JVM 时加上这几个参数:
- -XX:+TraceClassLoading:记录每个类从哪来、什么时候加载
- -XX:+TraceClassUnloading:只有开了这个,才能看到“谁被卸载了”
- -Xlog:gc+metaspace*=debug(JDK 10+)或 -XX:+PrintGCDetails(老版本):观察元空间使用量和 GC 行为
重点盯紧:重启或热部署后,加载次数猛增但卸载始终为 0,基本可以断定类加载器卡住了,卸载链被阻断。
用 MAT 挖出谁在“拽着”类加载器
拿到堆转储(比如 OOM 后自动生成的 .hprof 文件),用 Eclipse MAT 打开:
- 点菜单 → Class Loader Explorer,列出所有类加载器
- 右键可疑实例 → Path to GC Roots → 勾选 exclude weak/soft references
常见阻断点有三类:
- 线程池里的线程长期持有 Thread Context ClassLoader(TCCL),却没在任务结束时恢复
- 静态缓存(如 public static Map, ?> CACHE)存了某个业务类的实例,而该实例内部又持有了 Class 或 ClassLoader 引用
- 第三方库(CGLIB、Javassist、某些 ORM 或 AOP 框架)把 ClassLoader 存进自己的内部缓存,但没提供清理机制
修复要从代码和参数两头入手
调参只是临时缓解,根治得切断引用链:
- 涉及 TCCL 的地方,必须用 try-finally 恢复原始加载器:
Thread.currentThread().setContextClassLoader(originalCL); - 检查所有自定义类加载器,确保没有静态集合长期持有它的引用
- 对热部署场景(如 Spring Boot DevTools),启用类卸载支持:
-XX:+CMSClassUnloadingEnabled(CMS)或 -XX:+ZUnloading(ZGC) - 合理设置元空间边界:
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











