Android内存管理与GC算法详解

49 阅读4分钟

坐稳了,让我们要用一个超有趣的故事来给你讲清楚Android的GC原理。咱们把内存管理比作一个"游乐园管理"的故事!

🎢 游乐园内存管理大冒险

📖 故事背景

想象Android虚拟机是一个巨大的游乐园:

  • 对象 = 游客
  • 内存 = 游乐园的座位
  • GC = 游乐园管理员
public class Visitor {
    String name;        // 游客名字
    Visitor friend;     // 游客的朋友(对象引用)
    boolean isPlaying;  // 是否在玩耍
}

🔍 判断对象是否被回收的"三大法宝"

1. 引用计数法 - "数朋友法"

// 早期的做法:每个对象记录自己被引用的次数
class ReferenceCounting {
    private int referenceCount = 0;
    
    public void addReference() {
        referenceCount++;
    }
    
    public void removeReference() {
        referenceCount--;
        if (referenceCount == 0) {
            recycle(); // 没有朋友了,可以回收!
        }
    }
    
    private void recycle() {
        System.out.println("这个游客没人陪,可以回家了!");
    }
}

问题:循环引用!就像两个游客互相认为是对方的朋友,但其实他们都在孤岛上:

Visitor zhangsan = new Visitor("张三");
Visitor lisi = new Visitor("李四");

zhangsan.friend = lisi;  // 张三说李四是朋友
lisi.friend = zhangsan;  // 李四说张三是朋友

// 两人互相引用,但实际没有外部引用,应该回收却回收不了!

2. 可达性分析法 - "社交网络排查法"

这是Android真正使用的方法!就像检查游客是否在"社交网络"中:

public class ReachabilityAnalyzer {
    // GC Roots - 游乐园的"重要地标"
    private Set<Object> gcRoots = new HashSet<>();
    
    public void addGCRoot(Object root) {
        gcRoots.add(root);  // 添加GC根节点
    }
    
    public boolean isReachable(Object obj) {
        Set<Object> visited = new HashSet<>();
        return checkReachable(obj, visited);
    }
    
    private boolean checkReachable(Object obj, Set<Object> visited) {
        if (gcRoots.contains(obj)) {
            return true;  // 直接连接到重要地标!
        }
        
        visited.add(obj);
        
        // 递归检查所有引用关系
        for (Object reference : getReferences(obj)) {
            if (!visited.contains(reference) && checkReachable(reference, visited)) {
                return true;
            }
        }
        
        return false;  // 孤立的游客,可以回收!
    }
}

3. Finalize机制 - "最后救命稻草"

class Visitor {
    // ... 其他代码 ...
    
    @Override
    protected void finalize() throws Throwable {
        try {
            if (this.isPlaying) {
                System.out.println("等等!我还在玩过山车!");
                // 最后一次自救机会
                saveMeIfYouCan();
            }
        } finally {
            super.finalize();
        }
    }
    
    private void saveMeIfYouCan() {
        // 如果在这里重新建立引用,可以避免被回收
        if (someImportantVisitor != null) {
            someImportantVisitor.friend = this;
        }
    }
}

🎯 Android虚拟机的GC算法

1. 标记-清除算法 - "贴标签清理法"

class MarkSweepGC {
    public void collectGarbage() {
        // 第一阶段:标记
        System.out.println("=== 开始标记阶段 ===");
        markFromRoots();
        
        // 第二阶段:清除
        System.out.println("=== 开始清除阶段 ===");
        sweepUnmarked();
    }
    
    private void markFromRoots() {
        // 从GC Roots开始标记所有可达对象
        Queue<Object> toVisit = new LinkedList<>(getGCRoots());
        
        while (!toVisit.isEmpty()) {
            Object current = toVisit.poll();
            if (!isMarked(current)) {
                markObject(current);  // 贴上"存活"标签
                
                // 继续标记引用的对象
                for (Object reference : getReferences(current)) {
                    toVisit.offer(reference);
                }
            }
        }
    }
    
    private void sweepUnmarked() {
        for (Object obj : getAllObjects()) {
            if (!isMarked(obj)) {
                System.out.println("回收对象: " + obj);
                freeMemory(obj);  // 释放内存
            } else {
                unmarkObject(obj); // 清除标记,为下次GC准备
            }
        }
    }
}

2. 复制算法 - "搬家整理法"

class CopyingGC {
    private MemorySpace fromSpace = new MemorySpace("From空间");
    private MemorySpace toSpace = new MemorySpace("To空间");
    
    public void collectGarbage() {
        System.out.println("开始复制GC:把所有活跃对象搬到新家!");
        
        // 从GC Roots开始,把所有活跃对象复制到To空间
        copyActiveObjects();
        
        // 交换From和To空间
        MemorySpace temp = fromSpace;
        fromSpace = toSpace;
        toSpace = temp;
        
        // 清空旧的To空间(现在的From空间)
        toSpace.clear();
    }
    
    private void copyActiveObjects() {
        // 使用Cheney算法进行广度优先复制
        // ... 具体实现 ...
    }
}

3. 标记-整理算法 - "停车场挪车法"

class MarkCompactGC {
    public void collectGarbage() {
        // 第一阶段:标记(和标记-清除一样)
        markFromRoots();
        
        // 第二阶段:计算新位置
        System.out.println("计算每个对象的新位置...");
        computeNewLocations();
        
        // 第三阶段:更新引用
        System.out.println("更新所有指针指向新地址...");
        updateReferences();
        
        // 第四阶段:移动对象
        System.out.println("把对象移动到新位置...");
        compactObjects();
    }
}

🏆 Android虚拟机的"王牌算法"

ART虚拟机(Android 5.0+)主要使用:分代收集 + 并发标记-清除

class ARTGenerationalGC {
    // 年轻代 - 新游客区域
    private MemoryRegion youngGeneration = new MemoryRegion("年轻代");
    
    // 老年代 - 常客区域  
    private MemoryRegion oldGeneration = new MemoryRegion("老年代");
    
    public void handleAllocation(Request request) {
        if (youngGeneration.hasSpace()) {
            // 在年轻代分配
            youngGeneration.allocate(request);
        } else {
            // 年轻代满了,触发Minor GC!
            System.out.println("年轻代满了,触发Minor GC!");
            minorGC();
            
            if (!youngGeneration.hasSpace()) {
                // 如果还不够,晋升到老年代
                promoteToOldGeneration(request);
            }
        }
        
        // 检查老年代是否也需要GC
        if (oldGeneration.isAlmostFull()) {
            System.out.println("老年代也快满了,触发Major GC!");
            majorGC();
        }
    }
    
    private void minorGC() {
        // 对年轻代使用复制算法(快速)
        copyingGC.collect(youngGeneration);
    }
    
    private void majorGC() {
        // 对老年代使用并发标记-清除(减少停顿)
        concurrentMarkSweepGC.collect(oldGeneration);
    }
}

🕒 GC调用时序图

deepseek_mermaid_20251010_7d7349.png

🎪 现实中的游乐园管理策略

分代收集原理

class GenerationalStrategy {
    // 假设对象有"年龄"概念
    class ObjectWithAge {
        Object data;
        int age = 0; // 经过的GC次数
    }
    
    public void processGC() {
        // 年轻代GC很频繁(新游客流动性大)
        if (youngGenerationGCCounter++ >= YOUNG_GC_THRESHOLD) {
            doYoungGC();
        }
        
        // 老年代GC较少(常客比较稳定)
        if (oldGenerationGCCounter++ >= OLD_GC_THRESHOLD) {
            doOldGC();
        }
    }
    
    private void doYoungGC() {
        // 复制算法:快速清理短期对象
        for (Object obj : youngGeneration.getObjects()) {
            if (obj.isReachable()) {
                obj.age++;
                if (obj.age >= PROMOTION_AGE) {
                    // 晋升到老年代
                    promoteToOldGeneration(obj);
                } else {
                    // 留在年轻代,复制到Survivor区
                    copyToSurvivorSpace(obj);
                }
            }
            // 不可达对象直接被回收
        }
    }
}

💡 关键要点总结

  1. 判断对象存活:主要用可达性分析,从GC Roots出发
  2. GC Roots包括:栈帧中的局部变量、静态变量、JNI引用等
  3. Android主要算法:分代收集 + 并发标记-清除
  4. 优势:年轻代快速回收,老年代并发处理减少停顿
  5. 演进:从Dalvik的标记-清除到ART的更先进算法

现在你明白了吧?就像游乐园管理员一样,GC要聪明地判断哪些游客(对象)还在玩,哪些已经离开,然后高效地回收空间给新游客使用!