Android Class 回收原理及代码演示

要搞懂「Class 会不会回收」和「怎么回收」,咱们先抛开枯燥的术语,从一个 「图书馆管理员与临时图书」 的故事讲起,再结合代码和时序图拆解原理,保证小白也能秒懂!

一、先讲故事:Class 回收的本质是「没人要的临时资产」

把 JVM 想象成一个 「手机系统图书馆」,里面有这些角色:

  • JVM GC:图书馆清洁工,专门清理没人用的 "垃圾";

  • 类加载器:图书馆管理员,负责 "引进图书"(加载 Class);

    • 「系统管理员」(Bootstrap/Extension/Application ClassLoader):官方固定员工,管的是《系统操作手册》《基础工具集》这类 "常备书"(如 java.lang.Stringandroid.os.Bundle),这些书天天有人用,清洁工绝不会收;
    • 「临时管理员」(自定义 ClassLoader):临时雇来的,负责引进一些 "小众临时书"(如插件类、热修复类),这些书用完没人要了,就会被清洁工收走;
  • Class 对象:图书的 "实体副本",管理员引进书后,会生成这个副本供人使用;

  • 普通对象:读者借走的 "书的复印件",用完还回去(引用置 null),很快会被回收,但 "实体副本"(Class)是否回收,要看管理员和副本本身有没有人要。

核心结论(故事版):

  1. Class 能回收吗? 能!但只限于「临时管理员引进的临时书」(自定义 ClassLoader 加载的 Class)。「系统管理员的常备书」(系统类)永远不会被回收,因为管理员和书一直有人用。

  2. 怎么让 Class 被回收? 必须满足两个 "没人要":

    • ① 「临时管理员」没人要了(自定义 ClassLoader 实例无任何引用);
    • ② 「图书实体副本」没人要了(Class 对象无任何引用:没有实例、没有静态变量持有、没有反射引用等)。

二、代码实战:亲眼见证 Class 回收

咱们用代码模拟 "临时管理员引进临时书,用完回收" 的过程,关键是用 弱引用(WeakReference) 观察 Class 是否被回收(弱引用不会阻止 GC 回收,对象被回收后,弱引用会返回 null)。

步骤 1:准备 "临时书"(TestClass)

这是一本 "临时书",没有静态变量(避免持有 Class 引用),只有一个简单方法:

java 复制代码
// 临时书:TestClass(没有静态变量,避免干扰回收)
public class TestClass {
    public void sayHello() {
        System.out.println("临时书:Hello,我是被临时管理员加载的!");
    }
}

步骤 2:准备 "临时管理员"(自定义 ClassLoader)

自定义一个 ClassLoader,负责加载 "临时书"。注意:必须重写 findClass 而非 loadClass,因为 loadClass 默认遵循「双亲委派」(先让系统管理员加载),我们要强制用 "临时管理员" 加载,所以只在系统管理员找不到时,自己加载:

java 复制代码
// 临时管理员:CustomClassLoader(自定义类加载器)
public class CustomClassLoader extends ClassLoader {
    // 从字节数组加载 Class(模拟"引进临时书")
    public Class<?> loadCustomClass(String className, byte[] classBytes) {
        // 1. 先检查是否已加载过(避免重复加载)
        Class<?> loadedClass = findLoadedClass(className);
        if (loadedClass != null) {
            return loadedClass;
        }
        // 2. 双亲委派:让父加载器(系统管理员)先找,找不到再自己加载
        try {
            loadedClass = getParent().loadClass(className);
        } catch (ClassNotFoundException e) {
            // 父加载器找不到,自己加载(核心:临时管理员的工作)
            loadedClass = defineClass(className, classBytes, 0, classBytes.length);
        }
        return loadedClass;
    }
}

步骤 3:模拟 "借书、用书、还书、回收"

核心逻辑:用临时管理员加载书 → 借书用 → 用完后把 "书的复印件""实体副本""管理员" 都置为 null → 叫清洁工(GC)来清理 → 观察 Class 是否被回收:

java 复制代码
import java.lang.ref.WeakReference;

public class ClassRecyclerDemo {
    public static void main(String[] args) throws Exception {
        // 1. 准备"临时书"的字节码(这里简化:从已编译的 TestClass.class 读取)
        byte[] testClassBytes = getClassBytes("TestClass.class");

        // 2. 雇"临时管理员"(创建 CustomClassLoader 实例)
        CustomClassLoader tempLoader = new CustomClassLoader();

        // 3. 管理员引进"临时书"(加载 TestClass,生成 Class 对象)
        Class<?> testClass = tempLoader.loadCustomClass("TestClass", testClassBytes);

        // 4. 借"书的复印件"(创建 TestClass 实例,用一下)
        Object testInstance = testClass.getConstructor().newInstance();
        testClass.getMethod("sayHello").invoke(testInstance); // 输出:临时书:Hello...

        // 5. 用完了!把"复印件""实体书""管理员"都置为 null(关键:切断所有引用)
        testInstance = null;
        WeakReference<Class<?>> testClassWeakRef = new WeakReference<>(testClass); // 弱引用观察回收
        testClass = null; // 切断 Class 对象的强引用
        tempLoader = null; // 切断临时管理员的强引用

        // 6. 叫清洁工(GC)来巡逻(注意:System.gc() 是"建议",不是强制,但演示足够)
        System.out.println("=== 叫清洁工(GC)来清理 ===");
        System.gc();
        Thread.sleep(1000); // 等 GC 执行完

        // 7. 观察:"临时书"(TestClass)是否被回收
        if (testClassWeakRef.get() == null) {
            System.out.println("✅ 临时书(TestClass)被回收了!");
        } else {
            System.out.println("❌ 临时书(TestClass)没被回收...");
        }
    }

    // 辅助方法:读取 .class 文件的字节数组(模拟加载类文件)
    private static byte[] getClassBytes(String className) throws Exception {
        try (InputStream is = ClassRecyclerDemo.class.getClassLoader().getResourceAsStream(className);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buf = new byte[1024];
            int len;
            while ((len = is.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            return baos.toByteArray();
        }
    }
}

代码运行结果(关键):

text 复制代码
临时书:Hello,我是被临时管理员加载的!
=== 叫清洁工(GC)来清理 ===
✅ 临时书(TestClass)被回收了!

如果我们加个 静态变量 干扰(比如在 TestClass 里加 private static Object obj = new Object();),结果会变成 ❌ 没被回收------ 因为静态变量属于 Class 对象,会一直持有 Class 的引用,GC 无法回收!

三、原理拆解:Class 回收的 3 个核心条件

通过故事和代码,我们能提炼出 Class 被回收的 3 个严格条件(缺一不可):

  1. Class 对象无任何强引用 :没有地方再用这个 Class 了(比如 testClass = null,没有反射持有它,没有 Class.forName 残留引用);
  2. 加载它的 ClassLoader 无任何强引用 :「临时管理员」没人要了(tempLoader = null),因为 Class 对象内部会持有对它的加载器的引用(testClass.getClassLoader() 能拿到),如果加载器还在,Class 肯定不会被回收;
  3. 该 Class 的所有实例都已被回收 :没有 "书的复印件" 在外面(testInstance = null),实例会持有对 Class 的引用(instance.getClass()),实例没回收,Class 也没法回收。

为什么系统类(如 String)永远不回收?

因为加载它们的「系统管理员」(如 Application ClassLoader)会被 JVM 一直持有引用(JVM 运行期间需要用这些管理员加载系统类),所以系统管理员永远不回收,它加载的 Class 也永远不回收。

四、时序图:Class 回收的完整流程

用 Mermaid 时序图直观展示整个过程(从加载到回收):

JVM 清洁工(GC)临时书(TestClass 的 Class 对象)临时管理员(CustomClassLoader)应用程序(你)JVM 清洁工(GC)临时书(TestClass 的 Class 对象)临时管理员(CustomClassLoader)应用程序(你)父加载器找不到,自己加载创建 CCL 实例(雇临时管理员)调用 loadCustomClass("TestClass")(要引进书)双亲委派:父加载器查找(系统管理员找)调用 defineClass()(生成 Class 对象)返回 TestClass 对象(书到手)创建实例、调用方法(用书)实例置 null(还复印件)Class 置 null(断实体书引用)CCL 置 null(让管理员下班)调用 System.gc()(叫清洁工)检测 CCL 无引用(标记管理员)检测 TestClass 无引用 + 加载器已标记(标记书)回收 CCL(清理管理员)回收 TestClass(清理书)检查 WeakReference(看书没了)输出"✅ 临时书被回收"

五、总结:必记的 3 句话

  1. Class 能回收,但只限于 "临时的" :自定义 ClassLoader 加载的 Class 能回收,系统类(如 String)永远不回收;
  2. 回收的关键是 "没人要" :Class 对象、加载它的 ClassLoader、所有实例,必须都没有强引用;
  3. 别踩坑:静态变量、反射引用会 "粘住" Class,导致无法回收,想回收就先清掉这些引用。

下次有人问你 "Class 会不会回收",你就把这个图书馆的故事讲给他听,再甩上代码和时序图,保证他秒懂!

相关推荐
前端 贾公子6 小时前
《Vuejs设计与实现》第 18 章(同构渲染)(上)
android·flutter
LiuYaoheng6 小时前
【Android】Android 的三种动画(帧动画、View 动画、属性动画)
android·java
苏苏码不动了6 小时前
Android Studio 虚拟机启动失败/没反应,排查原因。提供一种排查方式。
android·ide·android studio
weixin_456904277 小时前
YOLOv11安卓目标检测App完整开发指南
android·yolo·目标检测
W.Buffer9 小时前
通用:MySQL主库BinaryLog样例解析(ROW格式)
android·mysql·adb
qiushan_9 小时前
【Android】【Framework】进程的启动过程
android
用户2018792831679 小时前
Java经典一问:String s = new String("xxx");创建了几个String对象?
android
用户2018792831679 小时前
用 “建房子” 讲懂 Android 中 new 对象的全过程:从代码到 ART 的魔法
android
用户2018792831679 小时前
JVM类加载大冒险:小明的Java奇幻之旅
android