Android 开发者对于 GC 既熟悉又陌生,听说过很多虎狼之词,对一些问题又不置可否;今天聊聊 Android 里的 GC,如果你对于下面的问题有兴趣又没答案,那你应该会有些收获:
- JVM、Dalvik、ART, 它们之间是什么关系?
- 所有版本的 Android 都是分代管理堆内存吗?
- 垃圾对象到底是怎么被回收的?
- 「内存抖动」你怕不怕?
- 作为一个应用层开发者,我真的需要关心 Android GC 吗?
前言:概念辨析
为了避免一些朋友不是很清楚概念,在正文开始之前,先简单辨析一下:
GC
GC,是指垃圾回收 (Garbage Collection),是一些语言管理内存的方式,如 Java 语言等;程序员不需要主动管理内存,程序运行时环境(虚拟机)会做垃圾回收的工作,就是在合适的时机 自动释放不再需要的内存。
与 GC 对应的是 native 语言,它需要程序员主动释放申请内存,忘记释放或者释放时机不合适都会产生问题。
GC Root
在 native 语言中,内存的申请和释放需要程序员来操作,做到正确地申请和释放内存就是程序员要考虑的问题。
在类似 Java 这种使用了 GC 的语言中,程序员不关心内存的释放。正确地释放内存就是 GC 的责任,GC 的原则是保证正确性的前提下,尽可能提升性能。 于是就使用了 GC Root 的机制,逻辑上就是:GC 认为 GC Root 以及它引用的对象是程序后面的可能会用到的,所以不会释放;没有被 GC Root 直接或间接引用的对象,后面一定不会被用到,可以被释放掉。
在 Java 中常被用于GC Root的类型如下:
- (函数未出栈时的)局部变量
- 静态变量
- 存活状态的线程
- Native 方法中 JNI 引用的对象
另外,Java 中内存泄漏的本质,就是对象 (不当地) 被GC Root 引用导致无法释放。
之所以会在这里提到 GC Root, 因为后面的流程中上来就先找 GC Root 集合,看完这个你就知道那是在干嘛了。
JVM / Dalvik / ART
JVM 是 Java 语言提出的虚拟机标准,基于这个标准各个厂商会有自己的实现。比如 Oracle 的 HotSpot,以及 Android 中的 DVM (Dalvik Virtual Machine) 和 ART (Android Runtime) 两个实现。其中,
- Dalvik 是 Android 4.4 (Kitkat) 以及之前的版本的虚拟机;
- ART 在 Android 4.4 (Kitkat) 引入,并在Android 5.0 (Lollipop) 开始取代Dalvik.
分代管理
分代管理是很多 JVM 虚拟机对于堆 (heap) 内存的管理机制,最为人熟知的是新生代 (Young Generation)、老年代 (Old Generation) 和永久代 (Permanent Generation) 这一组名词,这也是很多人讲 GC 时的默认组合。事实上,这一组名词是 Oracle 的 HotSpot 中对于 JDK 1.7及之前的实现方式。至于其他的 JVM 实现,可以选择是否采用分代管理的机制。
比如 Dalvik 就没有采用分代管理的机制,ART 在 Android 8 (Oero) 和 9 (Pie) 版本未使用分代管理、其他的版本又采用了该机制。
在分代管理的虚拟机中,新生代和老年代正如它们的名字一样,分别存储了新创建的对象和存活了很久的对象;新的对象会在新生代中创建,经过一定次数的 GC 后依然存活的对象,会被复制到老年代中(有些虚拟机新生代分为更多的区域,以达到的最佳的戏能表现)。
前言结束,现在进入今天最重要的部分了
Android GC 的演进
史前时代的 Dalvik
Dalvik 虚拟机随着 Android 一起诞生,一直到 Android 4.4。
HTC G1 是第一款 Android 设备,内存为192MB,应用程序可用的堆内存仅为30MB左右,Dalvik 就是在这样的环境下诞生的。它的设计原则中,节省空间 (尽量让程序跑起来) 是第一优先级。
内存分配
分配内存时,Dalvik 使用的算法是 dlmalloc
(以 java.util.concurrent
作者 Doug Lea 命名的算法),使用了单独的进程来分配内存,内存的分配效率较低。
-
在整个堆中寻找适合分配的内存
-
找到了适合分配的内存
-
内存分配成功
-
上面的图中给出了顺利分配内存的流程,如果当前堆中没有合适分配的内存,就会触发一个
GC_FOR_ALLOC
进入GC流程。