Android VM 03.ART

1.ART的改进

1.1 AOT编译:

Ahead-of-time(AOT)是相对于Just-in-time(JIT)而言的。JIT是在运行时进行字节码到本地机器码的编译,这也是为什么Java普遍被认为效率比C++差的原因。无论是解释器的解释还是运行过程中即时编译,都比C++编译出的本地机器码执行多了一个耗费时间的过程。而AOT就是向C++编译过程靠拢的一项技术:当APK在安装的时候,系统会通过一个名称为dex2oat的工具将APK中的dex文件编译成包含本地机器码的oat文件存放下来。这样做之后,在程序执行的时候,就可以直接使用已经编译好的机器码以加快效率。

1.2垃圾回收的改进:

GC(Garbage Collection)是虚拟机非常重要的一个特性,因为它的实现好坏会影响所有在虚拟机上运行的应用。GC实现得不好可能会导致画面跳跃,掉帧,UI响应过慢等问题。

ART的垃圾回收机制相较于Dalvik虚拟机有如下改进:
  • 将GC的停顿由2次改成1次
  • 在仅有一次的GC停顿中进行并行处理
  • 在特殊场景下,对于近期创建的具有较短生命的对象消耗更少的时间进行垃圾回收
  • 改进垃圾收集的工效,更频繁的执行并行垃圾收集
  • 对于后台进程的内存在垃圾回收过程进行压缩以解决碎片化的问题

AOT编译是在应用程序安装时就进行的工作,下图描述了Dalvik虚拟机与(Android 5.0上的)ART虚拟机在安装APK时的区别:

image

两种虚拟机上安装APK时的流程

从这幅图中我们看到:

  • 在Dalvik虚拟机上,APK中的Dex文件在安装时会被优化成odex文件,在运行时,会被JIT编译器编译成native代码。

  • 而在ART虚拟机上安装时,Dex文件会直接由dex2oat工具翻译成oat格式的文件,oat文件中既包含了dex文件中原先的内容,也包含了已经编译好的native代码。

dex2oat生成的oat文件在设备上位于/data/dalvik-cache/目录下。同时,由于32位和64位的机器码有所区别,因此这个目录下还会通过子文件夹对oat文件进行分类。例如,手机上通常会有下面两个目录:

  • /data/dalvik-cache/arm/
  • /data/dalvik-cache/arm64/

2. ART虚拟机垃圾回收概述

ART 有多个不同的 GC 方案,这些方案包括运行不同垃圾回收器。默认方案是 CMS(Concurrent Mark Sweep,并发标记清除)方案,主要使用粘性(sticky)CMS 和部分(partial)CMS。粘性CMS是ART的不移动(non-moving )分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分,并且只能回收自上次GC后分配的对象。除CMS方案外,当应用将进程状态更改为察觉不到卡顿的进程状态(例如,后台或缓存)时,ART 将执行堆压缩。

除了新的垃圾回收器之外,ART 还引入了一种基于位图的新内存分配程序,称为 RosAlloc(插槽运行分配器)。此新分配器具有分片锁,当分配规模较小时可添加线程的本地缓冲区,因而性能优于 DlMalloc。

与 Dalvik 相比,ART CMS垃圾回收计划在很多方面都有一定的改善:

  • 与Dalvik相比,暂停次数2次减少到1次。Dalvik的第一次暂停主要是为了进行根标记。而在ART中,标记过程是并发进行的,它让线程标记自己的根,然后马上就恢复运行。
  • 与Dalvik类似,ART GC在清除过程开始之前也会暂停1次。两者在这方面的主要差异在于:在此暂停期间,某些Dalvik的处理阶段在ART中以并发的方式进行。这些阶段包括 java.lang.ref.Reference处理、系统弱引用清除(例如,jni全局弱引用等)、重新标记非线程根和卡片预清理。在ART暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。
  • 相对于Dalvik,ART GC改进的最后一个方面是粘性 CMS回收器增加了GC吞吐量。不同于普通的分代GC,粘性 CMS 不会移动。年轻对象被保存在一个分配堆栈(基本上是 java.lang. Object 数组)中,而非为其设置一个专用区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。

ART GC 与 Dalvik 的另一个主要区别在于,ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用的内存用量。目前,堆压缩由 ActivityManager 进程状态变化来触发。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。这样可让 ART 执行一些会导致应用线程长时间暂停的操作,如压缩和监控器紧缩。目前正在使用的两个移动GC是同构空间压缩(Homogeneous Space Compact)和半空间(Semispace Compact)压缩。

  • 半空间压缩将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存。额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。由于 CMS 仍在前台使用,且不能从碰撞指针空间中进行收集,因此当应用在前台使用时,半空间还要再进行一次转换。这种情况并不理想,因为它可能引起较长时间的暂停。

  • 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。

3. ART GC REASON

在ART虚拟机中,很多场景都会触发垃圾回收的执行。ART代码中通过GcCause这个枚举进行描述,包括下面这些事件:

常量 说明
kGcCauseForAlloc 当要分配内存的时候发现内存不够的情况下引起的GC,这种情况下的GC会Stop World.由于是并发GC,其它线程都会停止,直到GC完成。
kGcCauseBackground 当内存达到一定的阀值的时候会去出发GC,这个时候是一个后台GC,不会引起Stop World
kGcCauseExplicit 显示调用的时候进行的gc,如果ART打开了这个选项的情况下,在system.gc的时候会进行GC.
kGcCauseForNativeAlloc 由于native的内存分配
kGcCauseCollectorTransition 垃圾收集器发生了切换
kGcCauseHomogeneousSpaceCompact 当前景和后台收集器都是CMS时,发生了后台切换
kGcCauseClassLinker ClassLinker导致

另外,垃圾回收策略有三种类型:

  • Sticky 仅仅释放上次GC之后创建的对象
  • Partial 仅仅对应用程序的堆进行垃圾回收,但是不处理Zygote的堆
  • Full 会对应用程序和Zygote的堆都会进行垃圾回收

快速GC策略Sticky GC:Sticky Mark sweep只扫描自从上次GC后被修改过的堆空间,并且也只回收自从上次GC后分配的空间。Sticky是只回收kGcRetentionPolicyAlwaysCollect的space。不回收其他两个,因此sticky的回收的力度是最小的。作为最全面的full mark sweep, 上面的三个策略都是会回收的。

局部GC策略Partial GC:这是mark sweep收集器里使用的最少的GC收集策略。按照官方文档,一般是使用sticky mark sweep较多。这里有一个概念就是吞吐率,即一次GC释放的字节数和GC持续的时间(秒)的比值。由于一般是sticky mark sweep进行GC,所以当上次GC的吞吐率小于同时的partial mark sweep的吞吐率时,就会把下次GC收集器从sticky变成partial。但是在partial执行一次GC后,就仍然会恢复到stick mark sweep收集器。阅读源码发现,partial重写了父类的成员函数。

全局GC策略Full GC:对整个堆进行垃圾回收,除了image空间。

4. Dalvik and Art 运行时堆

不管是Dlavik还是ART,运行时堆都分为LinearAlloc(类似于ART的Non Moving Space)、Zygote Space和Alloc Space。

Dalvik中的Linear Alloc是一个线性内存空间,是一个只读区域,主要用来存储虚拟机中的类,因为类加载后只需要读的属性,并且不会改变它。把这些只读属性以及在整个进程的生命周期都不能结束的永久数据放到线性分配器中管理,能很好地减少堆混乱和GC扫描,提升内存管理的性能。

Android系统的第一个虚拟机由Zygote进程创建并且只有一个Zygote
Space。但是当Zygote进程在fork第一个应用程序进程之前,会将已经使用的那部分堆内存划分为一部分,还没有使用的堆内存划分为另一部分,也就是Allocation Space。但无论是应用程序进程,还是Zygote进程,当他们需要分配对象时,都是在各自的Allocation Space堆上进行。

Dalvik
  • Linear Alloc
  • Zygote Space Zygote进程和应用程序进程之间共享
  • Alloc Space Allocation Space每个进程独占
ART
  • Non Moving Space
  • Zygote Space
  • Alloc Space
  • Image Space 存放一些预加载类,类似于Dalvik中的Linear Alloc。与Zygote Space一样,在Zygote进程和应用程序进程之间共享
  • Large Obj Space 离散地址的集合,分配一些大对象,用于提高GC的管理效率和整体性能。

注意:Image Space的对象只创建一次,而Zygote Space的对象需要在系统每次启动时,根据运行情况都重新创建一遍。


Android上的ART虚拟机

Android GC 从dalvik到ART的改进分析

调试 ART 垃圾回收