类的回收大冒险:一场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王国里,做一个"懂得及时离开"的好类公民,才能让内存村保持整洁高效!🏡✨

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952717 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android