读书笔记,如需转载,请注明做者:Yuloran (t.cn/EGU6c76)android
本文分为两部分,第一部分为 《Garbage Collection in Android》 的翻译,第二部分简介 Android 虚拟机与 Java 虚拟机的差异。git
Colt McAnlis,Google 开发工程师。为便于写做,笔者将以第一人称视角对视频内容进行概述。github
不少高性能语言,如 C 和 C++ 都须要开发人员手动管理内存的分配与释放,但在代码量很大、业务逻辑很复杂时,很容易忘记释放已分配的内存,进而致使内存泄露。位于 Android Runtime 的 Dalvik(< Android 5.0) 或 ART(≥ Android 5.0)虚拟机的自动垃圾回收机制,将开放人员从手动管理内存的工做方式中解放了出来,从而提升了工做效率。但同时也隐藏了性能陷进,其中最须要关注的就是如何分配以及使用内存。算法
自动内存管理机制称为 Garbage Collection,是 John McCarthy 于1959 年提出的概念,用于解决 LISP 语言中的问题。它主要涉及两个原则:性能优化
想象一下,你分配了 20,000 个对象,那么怎么识别哪些对象是再也不访问的,或者何时应该触发 GC 事件呢?bash
这真是太难了!好在,自 GC 概念提出以后的 50 年里,咱们一直致力于提高垃圾收集器的性能,这也是为何 Android 的垃圾回收器比 John McCarthy 提出的复杂的多的缘由。架构
事实上,Android 系统根据所分配对象的类型以及 GC 时系统如何管理这些对象,将进程所使用的内存分红了多个空间。新分配的对象位于什么空间,取决于你的 Android Runtime 是什么版本,5.0 以上是 ART(Android Runtime)虚拟机,5.0如下是 Dalvik 虚拟机。并发
笔者注:上图具体含义见下文差别分析。工具
每一个空间都有大小限制(Set Size),系统会跟踪整个程序所占用的内存大小。当程序占用内存达到必定程度时,系统就会触发 GC 事件回收内存,以便未来分配给其它对象:布局
GC 事件在 Dalvik 虚拟机和 ART 虚拟机上的表现也不尽相同。在 Dalvik 虚拟机中,不少 GC 事件都是 "Stop the World Event":
而 ART 虚拟机扩展了并行 GC 算法,消除了大的 GC 停顿时间,可是在重要步骤上,仍是会有短暂的停顿:
尽管系统工程师作了大量优化来提高 GC 速度来减小卡顿,可是你的 App 仍然可能存在性能问题。咱们知道,Android 系统每 16 ms 就要渲染一帧,因此在一帧时间内,GC 时间越长,留给业务逻辑的时间就越短:
若是你的 GC 过于频繁(好比在循环中建立了大量临时对象)或 GC 时间过长,就会致使 1 帧的处理时间超过 16 ms 上限,这就会给用户形成卡顿、掉帧的视觉效果:
好在,Android Studio 的 Profiler 工具,能够用来查看内存使用状况以及内存分配状况。
不过,内存优化就是提及来简单,作起来难,因此你还须要观看如下视频来掌握更多的性能优化知识:
其实笔者主要想关注的这几个虚拟机在内存布局及 GC 算法方面的差别,至于 JVM、Dalvik、ART 各自对应的可执行文件格式(.jar、.dex、.elf)、字节码结构(class、dex、elf)这显然是不一样的。奈何关于 Android 虚拟机内存布局网上资料甚少,大部分只围绕运行时堆内存的分配及其 GC 算法来说,没有涉及虚拟机栈(方法执行模型)、常量池、方法区,因此这部分无法跟 Java 虚拟机进行对比。不过,万物之间是存在广泛联系的,没有东西能够凭空产生。既然 Java 虚拟机的方法执行模型都能跟 C 语言在概念上很类似,Dalvik 和 ART 天然也能够照此理解,细节上确定是不同的,毕竟指令集都不同。真想深究,只能看 虚拟机源码 了。
不钻牛角尖了,头疼。再来看下这个图:
这图描述的就是 Dalvik 和 ART 虚拟机对运行时堆的空间划分,这个在源码中都有对应的实现。 上图具体含义可参考:
或者使用 看云 阅读更方便。
总的来讲,就是 Android 虚拟机没有使用 HotSpot 虚拟机所采用的分代收集算法,而是采用了标记-清除或者标记-复制算法,这个能够在编译系统时指定,可参考《Android Garbage Collection/dalvik GC》,不过通常都是标记清除算法。
如下摘自 《Android虚拟机之Dalvik虚拟机》:
- 内存管理
◇ Java Object Heap 大小受限,如:16M/24M/32M/48M
◇ Bitmap Memory(External Memroy):大小计入 Java Object Heap
◇ Native Heap:大小不受限- 垃圾收集(GC)
◇ Mark:使用RootSet标记对象引用
◇ Sweep:回收没有被引用的对象- GingerBread(Android 2.3)以前
◇ Stop-the-word:也就是垃圾收集线程在执行的时候,其它的线程都中止
◇ Full heap collection:也就是一次收集彻底部的垃圾,一次垃圾收集形成的程序停止时间一般都大于 100ms- GingerBread(Android 2.3)以后
◇ Cocurrent:也就是大多数状况下,垃圾收集线程与其它线程是并发执行的
◇ Partial collection:也就是一次可能只收集一部分垃圾,一次垃圾收集形成的程序停止时间一般都小于 5ms
ART 虚拟机对并行 GC 进行了扩展,将堆内存划分红更多不一样类型的具体空间,使用不一样的 GC 算法以得到更短的 GC 停顿时间。
Dalvik 虚拟机,是 Google 等厂商合做开发的 Android 移动设备平台的核心组成部分之一。它能够支持已转换为 .dex(即“Dalvik Executable”)格式的 Java 应用程序的运行。.dex 格式是专为 Dalvik 设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 由 Dan Bornstein 编写的,名字来源于他的祖先曾经居住过的小渔村达尔维克(Dalvík),位于冰岛 Eyjafjörður。
大多数虚拟机包括 JVM 都是一种堆栈机器,而 Dalvik 虚拟机则是寄存器机。两种架构各有优劣,通常而言,基于堆栈的机器须要更多指令,而基于寄存器的机器指令更长。
从 Android 5.0 版起,Android Runtime(ART)取代 Dalvik 成为系统内默认虚拟机。
差别:
- Dalvik 虚拟机早期并无使用即时编译(JIT)技术。从 Android 2.2 开始, Dalvik 虚拟机也支持 JIT.
- Dalvik 虚拟机有本身的字节码,并不是使用 Java 字节码。
- Dalvik 基于寄存器,而 JVM 基于堆栈。
- Dalvik VM 透过 Zygote 进行类别的预加载,Zygote 会完成虚拟机的初始化,也是与 JVM 不一样之处。
Dalvik 虚拟机采用 Mark-Sweep 算法,不带压缩整理(Compact),因此比 Java 虚拟机更简单一些。
(a)GC 前的状态。示例中有一个 GC Root,全部对象都未被标记。
(b)GC 标记后的状态。在标记阶段,全部活动对象(Active Objects)都会被标记。
(c)GC 清除后的状态。全部垃圾已被回收,而且全部活动对象的标记状态都被重置为 false。
在 Dalvik(而不是 ART)中,每次垃圾回收都会将如下信息打印到 logcat 中:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
复制代码
示例:
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
复制代码
垃圾回收缘由
什么触发了垃圾回收以及是哪一种回收。可能出现的缘由包括:
释放量
今后次垃圾回收中回收的内存量。
堆统计数据
堆的可用空间百分比与(活动对象数量)/(堆总大小)。
外部内存统计数据
API 级别 10 及更低级别的外部分配内存(已分配内存量)/(发生回收的限值)。
暂停时间
堆越大,暂停时间越长。并发暂停时间显示了两个暂停:一个出如今回收开始时,另外一个出如今回收快要完成时。 在这些日志消息积聚时,请注意堆统计数据的增大(上面示例中的 3571K/9991K 值)。若是此值继续增大,可能会出现内存泄漏。
Android Runtime(缩写为ART),是一种在 Android 操做系统上的运行环境,由 Google 公司研发,并在 2013 年做为 Android 4.4 系统中的一项测试功能正式对外发布,在 Android 5.0 及后续 Android 版本中做为正式的运行时库取代了以往的 Dalvik 虚拟机。ART 可以把应用程序的字节码转换为机器码,是 Android 所使用的一种新的虚拟机。它与 Dalvik 的主要不一样在于:Dalvik 采用的是 JIT 技术,而 ART 采用 Ahead-of-time(AOT)技术。ART 同时也改善了性能、垃圾回收(Garbage Collection)、应用程序出错以及性能分析。
JIT 最先在 Android 2.2 系统中引进到 Dalvik 虚拟机中,在应用程序启动时,JIT 经过进行连续的性能分析来优化程序代码的执行,在程序运行的过程当中,Dalvik 虚拟机在不断的进行将字节码编译成机器码的工做。与 Dalvik 虚拟机不一样的是,ART 引入了 AOT 这种预编译技术,在应用程序安装的过程当中,ART 就已经将全部的字节码从新编译成了机器码。应用程序运行过程当中无需进行实时的编译工做,只须要进行直接调用。所以,ART 极大的提升了应用程序的运行效率,同时也减小了手机的电量消耗,提升了移动设备的续航能力,在垃圾回收等机制上也有了较大的提高。为了保证向下兼容,ART 使用了相同的 Dalvik 字节码文件(dex),即在应用程序目录下保留了 dex 文件供旧程序调用然而 .odex 文件则替换成了可执行与可连接格式(ELF)可执行文件。一旦一个程序被 ART 的 dex2oat 命令编译,那么这个程序将会指经过 ELF 可执行文件来运行。所以,相对于 Dalvik 虚拟机模式,ART 模式下 Android 应用程序的安装须要消耗更多的时间,同时也会占用更大的储存空间(指内部储存,用于储存编译后的代码),但节省了不少 Dalvik 虚拟机用于实时编译的时间。
Google 公司在 Android 4.4 中带来的 ART 模式仅仅是 ART 的一个预览版,系统默认仍然使用的是 Dalvik 虚拟机,4.4 上面提供的预览版 ART 相对于 Android 5.0 之后的 ART 运行时库有较大的不一样,尤为体如今兼容性上。
与 Dalvik 不一样,ART 不会为未明确请求的垃圾回收记录消息。只有在认为垃圾回收速度较慢时才会打印垃圾回收。更确切地说,仅在垃圾回收暂停时间超过 5ms 或垃圾回收持续时间超过 100ms 时。若是应用未处于可察觉的暂停进程状态,那么其垃圾回收不会被视为较慢。始终会记录显式垃圾回收。
ART 会在其垃圾回收日志消息中包含如下信息:
I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time(s)>
复制代码
示例:
I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms
复制代码
垃圾回收缘由
什么触发了垃圾回收以及是哪一种回收。可能出现的缘由包括:
垃圾回收名称
ART 具备能够运行的多种不一样的垃圾回收。
释放的对象
这次垃圾回收从非大型对象空间回收的对象数量。
释放的大小
这次垃圾回收从非大型对象空间回收的字节数量。
释放的大型对象
这次垃圾回收从大型对象空间回收的对象数量。
释放的大型对象大小
这次垃圾回收从大型对象空间回收的字节数量。
堆统计数据
空闲百分比与(活动对象数量)/(堆总大小)。
暂停时间
一般状况下,暂停时间与垃圾回收运行时修改的对象引用数量成正比。当前,ART CMS 垃圾回收仅在垃圾回收即将完成时暂停一次。移动的垃圾回收暂停时间较长,会在大部分垃圾回收期间持续出现。
若是您在 logcat 中看到大量的垃圾回收,请注意堆统计数据的增大(上面示例中的 25MB/38MB 值)。若是此值继续增大,且始终没有变小的趋势,则可能会出现内存泄漏。或者,若是您看到缘由为“Alloc”的垃圾回收,那么您的操做已经快要达到堆容量,而且将很快出现 OOM 异常。
这个网页好像没有英文版,可是有的中文解释又很别扭,好比“映像空间”,这是什么鬼?其实就是 Image Space...