【底层机制】Android GC -- 为什么要有GC?GC的核心原理?理解GC的意义

我们将从"为什么需要GC"开始,逐步深入到Android虚拟机中GC的具体实现和最佳实践。


一、 为什么要垃圾回收?

在程序运行时,我们会不断地创建对象(比如在Activity、Fragment中new一个对象),这些对象都占据着内存空间。如果只创建不销毁,内存迟早会被耗尽,导致程序崩溃(OutOfMemoryError)。

在C/C++中,内存需要程序员手动管理(malloc/free, new/delete),这很容易导致两种问题:

  1. 内存泄漏:忘记释放不再使用的内存。
  2. 悬空指针:释放了内存后,又去使用它。

GC就是为了解决这个问题而诞生的。它自动追踪和回收不再被使用的对象,释放其占用的内存,从而让程序员从繁琐的内存管理工作中解放出来,专注于业务逻辑。


二、 GC的核心基本原理:可达性分析

GC如何判断一个对象是否"存活"?

核心思想 :从一系列被称为 "GC Roots" 的对象出发,向下搜索,所走过的路径称为 "引用链"。如果一个对象到GC Roots没有任何引用链相连,则说明此对象是不可用的,可以被回收。

GC Roots 通常包括以下几种:

  1. 虚拟机栈中的局部变量:正在执行的各个线程的方法栈帧中的局部变量表所引用的对象。
  2. 本地方法栈中JNI引用的对象
  3. 方法区中静态属性引用的对象:类的静态变量。
  4. 方法区中常量引用的对象:比如字符串常量池里的引用。
  5. Java虚拟机内部的引用:如基本数据类型对应的Class对象,常驻的异常对象等。
  6. 被同步锁持有的对象

简单来说:如果一个对象,从任何"根"开始都找不到它,它就是垃圾。


三、 Android虚拟机中的GC

Android主要经历了两个虚拟机时代:DalvikART。它们的GC机制有显著不同。

1. Dalvik虚拟机(Android 5.0之前)的GC

Dalvik的GC相对简单粗暴,是应用卡顿的一个重要原因。

  • 标记-清除算法

    • 标记 :暂停所有用户线程(这被称为 "Stop-The-World"),从GC Roots开始,遍历所有存活的对象并打上标记。
    • 清除:遍历整个堆,回收所有未被标记的对象所占用的内存。
    • 缺点 :会产生内存碎片。回收后,内存空间是不连续的,当需要分配一个较大对象时,可能无法找到足够的连续内存,从而触发另一次GC。
  • GC类型

    • GC_FOR_MALLOC: 当堆上分配内存失败时触发的GC。
    • GC_CONCURRENT: 当堆内存达到一定阈值时,尝试在后台并发执行的GC,以减少应用停顿。
    • GC_EXPLICIT : 显式调用 System.gc() 触发的GC(强烈不建议,因为它会打乱虚拟机的GC计划)。

Dalvik GC的痛点Stop-The-World 时间较长,尤其是在GC_CONCURRENT的最后阶段,也会暂停所有线程进行清理工作,导致应用卡顿、掉帧

2. ART虚拟机(Android 5.0及之后)的GC

ART在安装时就将字节码预编译成本地机器码,其GC策略也更加先进和高效,主要目标是减少停顿时间

  • 主要改进
    1. 多阶段GC:将GC过程拆分成多个阶段,很多阶段可以与用户线程并发执行。
    2. 不同种类的GC :针对不同情况使用不同的回收策略。
      • 并发标记-清除:大部分标记和清除工作与用户线程并发进行,大大减少了停顿时间。
      • 压缩GC :为了解决内存碎片问题,ART会不定期地执行压缩GC 。它会移动存活的对象,将它们紧凑地排列在内存的一端,从而释放出大块的连续空闲内存。这个过程的 Stop-The-World 时间较长,但ART会尽量在后台或应用在后台时进行。
    3. 分代收集:这是现代GC算法的核心思想,ART也采用了。

四、 深入理解:分代收集理论

这是理解现代GC(包括ART)的关键。根据对象的存活周期,将Java堆划分为新生代老年代

1. 新生代
  • 存放对象:绝大多数新创建的对象。

  • 特点:"朝生夕死",大部分对象很快变得不可达。

  • 区域划分

    • Eden区:新对象基本都分配在这里。
    • Survivor区(两个)From SurvivorTo Survivor。用于存放每次Minor GC后存活下来的对象。
  • 回收过程(Minor GC)

    1. 新对象在Eden区分配。
    2. 当Eden区满时,触发一次 Minor GC
    3. 将Eden区和From Survivor中仍然存活的对象,一次性复制到To Survivor区。
    4. 同时,为这些存活的对象"年龄"+1(每熬过一次Minor GC,年龄就加1)。
    5. 清空Eden区和刚使用的From Survivor区。
    6. 最后,From SurvivorTo Survivor的角色互换。
    • 优点:速度非常快,因为只处理一小块区域。
    • 晋升 :当一个对象的年龄增长到一定程度(默认15),就会被移动到老年代
2. 老年代
  • 存放对象:经过多次Minor GC仍然存活的对象(即长时间存活的对象),以及大对象(可能直接进入老年代)。
  • 特点:对象存活率高。
  • 回收过程(Major GC / Full GC)
    • 当老年代空间不足时,会触发 Major GC ,通常会伴随一次 Minor GC ,因此也常被称为 Full GC
    • Full GC 会对整个堆(新生代 + 老年代)进行回收,停顿时间最长 ,是应用卡顿的主要元凶之一。我们的优化目标就是尽量避免或减少Full GC

五、 对Android开发的指导意义与实践

理解了GC原理,我们就能写出对GC更友好的代码,提升应用性能。

1. 内存泄漏是头号大敌

内存泄漏会导致对象永远无法被GC回收,最终引发OOM。

  • 常见场景
    • 非静态内部类/匿名类持有外部类引用:如Handler、Runnable等。如果它们在Activity销毁后仍被系统(如消息队列)持有,就会导致Activity泄漏。
    • 静态变量持有Context/View引用
    • 集合类未及时清理
    • 第三方库使用后未正确释放(如监听器、广播)。
  • 排查工具Android Profiler , LeakCanary
2. 避免创建不必要的对象

对象的创建和销毁都是有成本的。在性能敏感的代码段(如onDraw、循环体),应尽量避免创建临时对象。

  • 反面教材

    java 复制代码
    // 在onDraw中每次循环都创建新对象,会瞬间产生大量垃圾,频繁触发GC
    for (int i = 0; i < 1000; i++) {
        String temp = "Item " + i; // 不要这样做!
        canvas.drawText(temp, x, y, paint);
    }
  • 优化方案:将对象提升为成员变量,或使用对象池。

3. 谨慎使用 System.gc()

如前所述,显式调用GC会打乱虚拟机的优化策略,可能导致不必要的、耗时的Full GC。把内存管理的决策权交给虚拟机

4. 关注 onTrimMemory()

当系统内存不足时,会回调此方法。我们可以在这里释放一些非核心资源(如缓存图片),帮助系统减轻内存压力,从而降低自身进程被杀死和触发GC的概率。

总结

特性 Dalvik GC ART GC
核心目标 功能实现 减少停顿,提升性能
主要算法 标记-清除(为主) 并发标记-清除 + 分代收集 + 压缩
停顿时间 较长,易引起卡顿 显著缩短,更流畅
处理碎片 不处理,碎片化严重 通过压缩整理内存

作为Android开发者,理解GC原理的最终目的是:

  1. 写出高性能、低卡顿的代码:通过避免内存泄漏和减少不必要的对象分配。
  2. 快速定位和解决内存问题:当发生OOM或内存抖动时,能迅速找到根源。
  3. 建立良好的内存观:知道代码的每一行背后可能发生什么,做到心中有数。

希望这份详细的讲解能帮助你彻底理解Android GC!

相关推荐
東雪木3 小时前
Java基础语言进阶学习——1,JVM内存模型(堆、栈、方法区)
java·jvm·学习
用户69371750013844 小时前
⚡Kotlin 五大神器完全解析:let、with、run、apply、also 一次搞懂,面试官都笑了!
android·kotlin
QmDeve4 小时前
Android 使用液态玻璃(LiquidGlass)效果,真实的折射和色散效果
android·github
xhbh6665 小时前
【实战总结】MySQL日志文件位置大全:附查找脚本和权限解决方案
android·adb
Digitally5 小时前
6 种无误的方法:如何备份和恢复华为手机
android
ideaout技术团队6 小时前
android集成react native组件踩坑笔记(Activity局部展示RN的组件)
android·javascript·笔记·react native·react.js
shaominjin1237 小时前
单例模式:设计模式中的“独一无二“之道
android·单例模式·设计模式
千里马学框架7 小时前
windows系统上aosp15上winscope离线html如何使用?
android·windows·html·framework·安卓窗口系统·winscope
码住懒羊羊7 小时前
【C++】模板进阶 | 继承
android·java·c++