类的回收大冒险:一场Android王国的"断舍离"故事

故事开始:忙碌的Android王国

在Android王国里,有一个叫做"内存村"的地方,这里住着各种各样的类(Class)居民。每个类都有自己的房子(内存空间),房子里住着它们的家人(方法、变量等)。

有一天,内存村出现了住房危机------内存不够用了!村长GC(垃圾回收)决定要清理掉那些"没人用"的类房子。

主角登场:ClassLoader家族

在Android王国,有三个主要的ClassLoader家族:

java 复制代码
// ClassLoader家族族谱
public class ClassLoaderFamily {
    // 老祖宗:启动类加载器(C++实现,Java中看不到)
    
    // 大管家:系统类加载器
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    
    // 二管家:扩展类加载器
    ClassLoader extClassLoader = systemClassLoader.getParent();
    
    // 小管家:应用类加载器(我们APP的)
    ClassLoader appClassLoader = getClass().getClassLoader();
    
    // 还有各种自定义的ClassLoader
    class MyCustomClassLoader extends DexClassLoader {
        // 可以加载dex、apk中的类
    }
}

什么情况下类会被回收?

场景1:普通的类居民

java 复制代码
public class NormalClass {
    private static String staticData = "我是静态数据";
    
    public void doSomething() {
        System.out.println("我在工作");
    }
    
    // 当满足以下条件时,这个类可以被回收:
    // 1. 没有实例对象存在
    // 2. 加载它的ClassLoader被回收
    // 3. 没有其他地方引用这个Class对象
    // 4. 没有在其他地方被主动引用(如反射)
}

场景2:特殊的静态居民

java 复制代码
public class ClassWithStaticReference {
    // 这个静态引用会阻止类被回收!
    private static Object staticHolder = new Object();
    
    // 这个也会!
    private static final String IMPORTANT_DATA = "重要数据";
    
    // 但如果这样写,就不会阻止回收
    private static class WeakStaticHolder {
        private static WeakReference<Object> weakRef = 
            new WeakReference<>(new Object());
    }
}

类的生命周期:从出生到退休

java 复制代码
public class ClassLifecycle {
    // 1. 加载阶段 - 搬进内存村
    static {
        System.out.println("我正在被加载!");
    }
    
    // 2. 连接阶段 - 办理入住手续
    //   - 验证:检查身份证是否合法
    //   - 准备:分配静态变量内存
    //   - 解析:把符号引用转成直接引用
    
    // 3. 初始化阶段 - 安家落户
    //   - 执行<clinit>方法(静态代码块)
    
    // 4. 使用阶段 - 正常工作
    
    // 5. 卸载阶段 - 搬出内存村(满足条件时)
}

实战演示:如何观察类的回收

java 复制代码
public class ClassGCWatcher {
    
    public static void main(String[] args) throws Exception {
        watchClassGC();
    }
    
    public static void watchClassGC() throws Exception {
        // 创建自定义ClassLoader来加载测试类
        ClassLoader customLoader = new CustomClassLoader();
        
        // 加载我们的测试类
        Class<?> targetClass = customLoader.loadClass("com.example.TempClass");
        
        // 创建弱引用来监视Class对象
        WeakReference<Class<?>> classRef = new WeakReference<>(targetClass);
        
        // 清除所有强引用
        targetClass = null;
        customLoader = null;
        
        // 触发GC
        System.gc();
        System.runFinalization();
        
        // 检查类是否被回收
        if (classRef.get() == null) {
            System.out.println("🎉 类被成功回收了!");
        } else {
            System.out.println("❌ 类还在内存中");
        }
    }
    
    static class CustomClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 模拟从dex/apk中加载类
            byte[] classData = loadClassData(name);
            return defineClass(name, classData, 0, classData.length);
        }
        
        private byte[] loadClassData(String className) {
            // 这里应该是从文件读取类的字节码
            // 为了演示,我们返回空数组
            return new byte[0];
        }
    }
}

时序图:类的完整生命周期

阻止类回收的"钉子户"

java 复制代码
public class ClassGCPreventer {
    // 情况1:静态变量持有引用
    public static List<Object> staticList = new ArrayList<>();
    
    // 情况2:单例模式
    private static ClassGCPreventer instance = new ClassGCPreventer();
    public static ClassGCPreventer getInstance() { return instance; }
    
    // 情况3:线程持有引用
    private Thread livingThread = new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(1000);
                // 线程运行时,其上下文ClassLoader会阻止类回收
            } catch (InterruptedException e) {
                break;
            }
        }
    });
    
    // 情况4:JNI全局引用
    // native方法中创建的全局引用也会阻止回收
}

优化技巧:做个"好公民"类

java 复制代码
public class GoodCitizenClass {
    // 技巧1:使用弱引用避免内存泄漏
    private WeakReference<Context> weakContext;
    
    // 技巧2:及时清理静态引用
    public static void clearStaticResources() {
        staticCache.clear();
        staticHolder = null;
    }
    
    // 技巧3:避免在静态块中创建重对象
    private static Map<String, String> lightWeightCache = 
        new ConcurrentHashMap<>(); // 轻量级缓存
    
    // 技巧4:使用适当的ClassLoader
    public void loadClassWisely() {
        // 对于临时类,使用独立的ClassLoader
        ClassLoader tempLoader = new TemporaryClassLoader();
        // 用完及时置空
        tempLoader = null;
    }
    
    static class TemporaryClassLoader extends ClassLoader {
        @Override
        protected void finalize() throws Throwable {
            // 清理资源
            super.finalize();
        }
    }
}

Android特殊场景

java 复制代码
public class AndroidClassGC {
    // 场景1:插件化框架中的类卸载
    public void hotPatch() {
        // 使用独立的DexClassLoader加载补丁
        DexClassLoader patchLoader = new DexClassLoader(
            patchPath, optimizedDirectory, null, parentLoader);
        
        // 当需要卸载补丁时
        patchLoader = null;
        System.gc();
    }
    
    // 场景2:动态特性交付
    public void dynamicFeature() {
        // 使用SplitCompat
        // 动态特性有自己的ClassLoader
        // 当特性不再需要时可以整体卸载
    }
    
    // 场景3:WebView中的类管理
    public void webViewScenario() {
        WebView webView = new WebView(context);
        // WebView会创建自己的ClassLoader
        // 及时销毁WebView有助于类回收
        webView.destroy();
    }
}

总结:类的回收法则

  1. 必要条件

    • 类的所有实例都已被回收
    • 加载该类的ClassLoader已被回收
    • 该类对应的java.lang.Class对象没有被任何地方引用
  2. 常见阻碍

    • 静态变量持有对象引用
    • 单例模式长期持有
    • 线程未结束
    • JNI全局引用
    • 反射缓存
  3. 优化建议

    • 使用弱引用存储上下文
    • 及时清理静态资源
    • 为临时功能使用独立ClassLoader
    • 避免在静态块中创建重对象

记住,在Android王国里,做一个"懂得及时离开"的好类公民,才能让内存村保持整洁高效!🏡✨

相关推荐
用户2018792831676 小时前
Android Class 回收原理及代码演示
android
前端 贾公子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