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 会不会回收",你就把这个图书馆的故事讲给他听,再甩上代码和时序图,保证他秒懂!

相关推荐
走在路上的菜鸟1 小时前
Android学Dart学习笔记第四节 基本类型
android·笔记·学习
百锦再1 小时前
第21章 构建命令行工具
android·java·图像处理·python·计算机视觉·rust·django
skyhh3 小时前
Android Studio 最新版汉化
android·ide·android studio
路人甲ing..3 小时前
Android Studio 快速的制作一个可以在 手机上跑的app
android·java·linux·智能手机·android studio
携欢6 小时前
PortSwigger靶场之Web shell upload via path traversal靶场通关秘籍
android
消失的旧时光-194314 小时前
Android ADB指令大全详解
android·adb
ashcn200116 小时前
opengl 播放视频的android c++ 方案
android·c++ opengl es
abner.Li16 小时前
android 反编译
android
Digitally16 小时前
如何删除 realme 手机上的短信
android
2501_9160088916 小时前
提高 iOS 应用逆向难度的工程实践,多工具联动的全栈安全方案
android·安全·ios·小程序·uni-app·cocoa·iphone