前言
Linus Benedict Torvalds : RTFSC – Read The Funning Source Code
概述
针对 Android 的垃圾回收机制分为 Dalvik 虚拟机 和 ART(Android Runtime) 虚拟机两种,因为 Dalvik 虚拟机已经被 ART 取代,所以我们重点还是关注 ART虚拟机的垃圾回收情况。
GC 算法
Mark Sweep 算法
Mark Sweep 算法是 Dalvik GC 和 ART GC 的核心算法。
Mark-Sweep 算法分为 Mark 标记 和 Sweep 打扫 两个阶段。
简单来讲:Mark 阶段从根集开始递归地标记引用的对象,然后在 Sweep 阶段 对没有标记的对象占用的内存进行回收。
ps:根集对象,就是指在GC开始的瞬间,被全局变量、栈变量和寄存器等引用的对象。
在 Mark 阶段要求除了 GC 线程以外的所有线程都停止,以防止错误的标记对象。这种停止称为 Stop the world。因为会导致程序中止执行,造成停顿的现象,所以在改进后加入了 Concurrent GC,就是并行垃圾收集算法。
Copying 复制算法
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
Mark-Compact 标记-压缩算法
先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
分代
将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年代则采取标记-压缩算法。
Dalvik GC
Dalvik 是Google公司自己设计用于Android平台的虚拟机。是Android移动设备平台的核心组成部分之一。
它可以支持已转换为 dex格式的Java应用程序的运行,dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。
Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
我们简单的用一张图来说明 Dalvik GC 的大体流程:
在GingerBread之前,Dalvik虚拟使用的垃圾收集机制有以下特点:
- Stop-the-world,也就是垃圾收集线程在执行的时候,其它的线程都停止;
- Full heap collection,也就是一次收集完全部的垃圾;
- 一次垃圾收集造成的程序中止时间通常都大于100ms。
在GingerBread以及更高的版本中,Dalvik虚拟使用的垃圾收集机制得到了改进,如下所示:
- Cocurrent,也就是大多数情况下,垃圾收集线程与其它线程是并发执行的;
- Partial collection,也就是一次可能只收集一部分垃圾;
- 一次垃圾收集造成的程序中止时间通常都小于5ms。
GingerBread Android 2.3
ART GC
ART模式英文全称为:Android runtime,谷歌Android 4.4系统新增的一种应用运行模式。
ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
ART GC 与 Dalvik 的一个主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。目前正在使用的两个移动 GC 是同构空间压缩和半空间压缩。
- 半空间压缩将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存。额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。由于 CMS 仍在前台使用,且不能从碰撞指针空间中进行收集,因此当应用在前台使用时,半空间还要再进行一次转换。这种情况并不理想,因为它可能引起较长时间的暂停。
- 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。
OAT文件
OAT文件是Android运行时ART的核心,它是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。这使得我们无需重新编译原有的APK就可以让它正常地在ART里面运行,也就是我们不需要改变原来的APK编程接口。
主要流程
ART 的优点主要有三点:
- 标记自身:ART在对象分配时会将新分配的对象压入到Heap类的成员变量allocationstack描述的Allocation Stack中去,从而可以一定程度上缩减对象遍历范围。
- 预读取:对于标记Allocation Stack的内存时,会预读取接下来要遍历的对象,同时在取出来该对象后又会将该对象引用的其他对象压入栈中,直至遍历完毕。
- 减少Suspend时间:在Mark阶段是不会Block其他线程的,这个阶段会有Dirty数据,比如Mark发现不会使用的但是这个时候又被其他线程使用的数据,在Mark阶段也会处理一些Dirty数据而不是留在最后Block的时候再去处理,这样也会减少后面Block阶段对于脏数据的处理的时间。