满足什么条件时,一个 Java 类会被卸载?

#Java#面试八股

满足什么条件时,一个 Java 类会被卸载?

结论先行:类卸载要同时满足三个条件:该类的所有实例都已被回收、加载该类的 ClassLoader 已被回收、该类的 Class 对象没有任何引用(无反射调用、无静态字段被访问、无子类存活)。而且卸载发生在 Full GC 期间,粒度是「整个类加载器」,不是单个类——只要加载器活着,它加载的类就一个都卸不掉。

一、三个条件的关系

Class 对象 ── 被谁引用?
├── 反射调用:Method/Field/Constructor 缓存      → 不满足
├── 静态字段被访问:Class 的 mirror 被持有        → 不满足
├── 该类还有实例存活                              → 不满足
├── 该类的子类还在用                              → 不满足
└── 加载它的 ClassLoader 仍被强引用               → 不满足(最常见)

三个条件全满足 + Full GC 扫描到 → 卸载
条件检查方式典型破坏者
实例全部回收jmap -histo 中该类实例数为 0长生命周期缓存持有对象
ClassLoader 被回收MAT 中 loader 无 GC Root 路径静态字段持有 loader、线程 TCCL、JNI 全局引用
Class 对象无引用无反射缓存、无 Class.forName 结果留存框架的反射缓存、MethodHandle

二、为什么粒度是加载器

JVM 内部用「类加载器 + 类名」唯一确定一个类。类卸载时方法区里的元数据是按加载器整体释放的,所以热部署框架(如 Tomcat、Spring Boot DevTools)都会为每次部署新建一个 ClassLoader,丢掉旧 loader 的引用,等 Full GC 后整批类一起卸载。这也是为什么动态生成类太多会撑爆元空间——只要 loader 没被回收,生成的类就全留着。

public class UnloadDemo {
    static class Holder {
        static byte[] payload = new byte[4 * 1024 * 1024];
        static { System.out.println("Holder 初始化"); }
    }

    public static void main(String[] args) throws Exception {
        java.net.URLClassLoader loader = new java.net.URLClassLoader(
                new java.net.URL[]{new java.io.File("plugin/").toURI().toURL()},
                UnloadDemo.class.getClassLoader());

        Class<?> c = loader.loadClass("com.example.Plugin");
        Object instance = c.getDeclaredConstructor().newInstance();
        System.out.println("加载器:" + c.getClassLoader());

        c = null;                 // 断开 Class 引用
        instance = null;          // 断开实例引用
        loader.close();
        loader = null;            // 断开加载器引用(关键)

        System.gc();              // 需要 Full GC 才可能卸载
        Thread.sleep(300);
        System.out.println("已释放引用,等待卸载");
    }
}

三、观察卸载

# JDK 8:打印类卸载
java -XX:+TraceClassUnloading -cp . UnloadDemo

# JDK 9+:统一日志
java -Xlog:class+unload=info -cp . UnloadDemo

# 看元空间与已加载类数量变化
jcmd <pid> GC.class_stats | grep -c "com/example"
jstat -gcmetacapacity <pid> 1000 5

四、常见追问

追问回答要点
类卸载一定需要 Full GC 吗是的,只有整堆回收(含元空间扫描)时才可能判定类不可达
为什么 JDK 自带的类卸不掉由启动类加载器加载,而启动类加载器永远存活,条件二不满足
动态代理类会卸载吗会,前提是生成它的加载器(如 Proxy 用的自定义 loader)被回收;JDK 8 的 Proxy 类缓存在 WeakCache 中
什么现象说明类没卸载Metaspace 持续增长、jcmd GC.class_stats 中类数量只增不减、Full GC 后元空间不回落
热部署为什么容易 Metaspace OOM每次部署新建 loader,旧 loader 被线程、静态变量或 Timer 线程引用,类就永远留着

小结

「实例没了、加载器没了、Class 也没人引用」三条同时成立,才会在 Full GC 时卸载,而且是以加载器为单位批量释放。理解这一点,就能解释热部署框架的设计以及元空间泄漏的根因。

笔记加载中…