要完整追踪JVM类加载生命周期,需覆盖加载、验证、准备、解析、初始化、使用、卸载七阶段并接入可观测工具链;类加载后静态变量长期驻留元空间,热更新时易引发泄漏。

要完整追踪 JVM 类加载生命周期,关键不是只看代码逻辑,而是把加载、验证、准备、解析、初始化、使用、卸载这七个阶段,全部落到可观测的工具链上。类一旦加载进方法区(元空间),它的静态变量就长期驻留,不随对象销毁而释放——这点在热更新或插件卸载时特别容易出问题。
用 jcmd + VM.classHierarchy 抓加载起点
类加载的第一步是 加载(Loading),此时 JVM 会为类生成 Class 对象并存入元空间。你可以用以下命令实时观察哪些类刚被加载:
- jcmd <pid> VM.classHierarchy -all:列出当前所有已加载类及其加载器,含全限定名和类加载器哈希值
- jcmd <pid> VM.native_memory summary scale=MB:确认元空间内存增长是否与类加载节奏匹配
- 配合 -XX:+TraceClassLoading 启动参数,可直接在 stdout 打印每次加载的类名和时间戳
用 JFR 录制链接与初始化全过程
验证、准备、解析、初始化 这四个阶段统称“连接”(Linking)加初始化,JVM 不暴露中间状态,但 Java Flight Recorder(JFR)能捕获关键事件:
- 启用 -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr,settings=profile
- 录制中触发类首次主动使用(如 new 实例、访问 static 字段、Class.forName)
- 用 JDK 自带的 JDK Mission Control 打开 recording.jfr,筛选 jdk.ClassDefine、jdk.Initialization、jdk.Unloading 事件,就能看到每个类从字节码载入到
<clinit>执行完毕的精确时间线
用 MAT 或 jcmd 检查类卸载是否真正发生
类卸载(Unloading)不是自动发生的,必须同时满足三个条件:该类所有实例已被回收、该类的 Class 对象无引用、该类加载器本身可被回收。要验证是否卸载成功:
- 先用 jcmd <pid> VM.class_histogram 查看指定类是否还在统计列表中
- 再用 jcmd <pid> VM.native_memory detail scale=MB 观察元空间(Metaspace)用量是否回落
- 若怀疑泄漏,导出堆转储(jcmd <pid> VM.native_memory detail + jmap -dump:format=b,file=heap.hprof <pid>),用 MAT 的 Leak Suspects Report 分析 ClassLoader 实例的引用链
重点盯住静态变量生命周期
类变量(static field)的生命周期 = 类加载开始 → JVM 进程终止,它不依赖对象存在。这意味着:
- 即使你清空了所有对象引用,只要类没卸载,static final String(编译期常量)仍驻留元空间
- new Object() 这类运行期初始化的 static 引用,会强持有堆对象,导致 GC 无法回收
- 建议在关键模块退出时,显式置 null 静态引用,并调用 System.gc()(仅作提示,不保证立即执行),再结合 JFR 看 jdk.Unloading 是否触发


















