
为什么 jmap -clstats 无法在 JDK 8u292+ 和 JDK 17+ 上正常工作
因为从 JDK 8u292 开始,jmap -clstats 被默认禁用;JDK 17+ 更是直接移除了该选项(JEP 366)。你执行时大概率会看到:java.lang.UnsupportedOperationException: clstats is not supported in this JVM。这不是权限或参数写错的问题,而是 HotSpot 团队主动废弃了这个统计口径——它底层依赖的内部 API 已不可靠,且结果常包含重复类加载器或漏计系统类加载器。
jmap -clstats 的替代方案:用 jstat -compiler + jstat -gcmetacapacity 拼出近似视图
虽然不能直接拿到“每个类加载器实例加载了多少类”,但你可以组合两个命令逼近核心关注点:类加载行为是否异常、元空间是否承压。
-
jstat -compiler <pid></pid>:看 JIT 编译状态,间接反映类加载稳定性。若failed列持续增长,说明频繁动态生成/卸载类(如 Spring CGLIB、Groovy 脚本),可能连带引发类加载器泄漏 -
jstat -gcmetacapacity <pid></pid>:重点关注MC(当前元空间容量)、MU(已使用量)、CCSC/CCSU(压缩类空间),比-clstats更准确反映元空间真实水位 - 二者结合可判断:元空间占用高 + 编译失败多 → 很可能是大量匿名类或反射代理未被卸载,而非单纯“类太多”
真正要查“哪个类加载器占用了最多元空间”,得用 jmap -histo:live 配合堆转储分析
jmap -clstats 原本想回答的问题,其实本质是:有没有类加载器没被回收,还持有着大量类和静态字段?这必须落到对象引用链上才能确认。
- 先执行
jmap -dump:format=b,file=heap.hprof <pid></pid>获取实时堆快照 - 用
jhat heap.hprof启动分析服务,或用 VisualVM / Eclipse MAT 打开,按ClassLoader类型筛选实例 - 重点观察:
sun.misc.Launcher$AppClassLoader通常只有一个;但若发现多个org.springframework.boot.loader.LaunchedURLClassLoader或自定义URLClassLoader实例且loadedClassCount持续上升,基本就是泄漏源头
JDK 21 下唯一保留的类加载器统计入口:jcmd <pid> VM.native_memory summary scale=MB</pid>
如果你用的是 JDK 21(或启用了 -XX:NativeMemoryTracking=summary 的较新版本),jcmd 可以给出更底层的视角:
- 输出中
[class]行显示的是所有类元数据(含类加载器结构体本身)占用的本地内存总量,单位 MB - 它不区分加载器实例,但能告诉你:这部分内存是否在持续增长(比如每小时涨 50MB),从而反推是否存在类加载器泄漏
- 注意:这个值 ≠
MetaspaceSize,它是 JVM 本地内存分配器的原始统计,更接近 OS 看到的真实开销
真正难定位的从来不是“有多少个类加载器”,而是“为什么某个加载器死活不被 GC”。这类问题必须结合堆转储里的 ClassLoader 实例及其 parent 引用、静态字段持有的对象图来追,jmap -clstats 早就不是那把钥匙了。

















