天天看点

Android ART虚拟机

转载自:https://blog.csdn.net/luoshengyang/article/details/45017207

Android与ios相比,一直被人诟病它的流畅性。android的流畅性问题,有一部分原因就归结于它的应用程序和部分系统服务是运行虚拟机之上的,也就是运行在Dalvik虚拟机之上,而ios的应用程序和系统服务都是直接执行本地机器指令的。除了使用ATR替换Dalvik之外,我们也应当看到,Android从3。0开始,就不遗余力地改进系统的流畅性。例如,3.0增加了对应用程序2D UI的硬件加速渲染,也就是GPU渲染。在此之前,应用程序的2D UI一直都是使用软件渲染,也就是CPU渲染。又如4.1通过Project Butter,在UI架构中引用了VSYNC、Triple Buffer和HWComposer等技术,极大地提高UI的流畅性。

ART之所以会比Dalvik快,是因为ART执行的是本地机器指令,而Dalvik执行的是Dex字节码,通过解释器执行。尽管Dalvik也会对频繁执行的代码进行JIT生成本地机器指令来执行,但毕竟在应用程序运行的过程中将Dex字节码翻译成本地机器指令也会影响到应用程序本身的执行,因此即使Dalvik使用了JIT,也在一定程度上也比不上直接就可以执行本地机器指令的运行时。

Dalvik就是android 4.4及职权使用的虚拟机。它使用的是JIT(just in time)技术来进行代码转译,每次执行应用的时候,Dalvik将程序的代码编译为机器语言执行。随着硬件水平的发展以及人民对更高性能的需求,Dalvik虚拟机的不足日益突出。而应运而生的ART(Android Run Time)虚拟机,其处理机制根本上的区别是它采用AOT(Ahead of TIme)技术,会在应用程序安装时就转换成机器语言,不再在执行时解释,从而优化了应用运行的速度。在内存管理方面,ART也有比较大的改进,对内存分配和回收都做了算法优化,降低了内存碎片化程度,回收时间也得以缩短。

与Dalvik虚拟机垃圾回收机制一样,ART运行时垃圾收集机制也涉及到类似于Zygote堆、Active堆、Card Table、Heap Bitmap和Mark Stack等概念,如图一所示:

Android ART虚拟机

从图1可以看到,ART运行时堆划分为4个空间,分别是Image Space、Zygote Space、Allocation Space和Large Object Space。其中,Image Space 、Zygote Space、Allocation Space是在地址上连续的空间,称为Continuous Space,而Large ObjectSpace 是一些离散地址的集合,用来分配一些大对象,称为Discontinuous Space。

在Image Space和Zygote Space之间,隔着一段用来映射[email protected]@[email protected]文件的内存。[email protected]@[email protected]是一个OAT文件,它是由在系统启动类路径中的所有Dex文件翻译得到的,而Image Space空间就包含了那些需要预加载的系统类对象。这意味着需要预加载的类对象是在生成[email protected]@[email protected]这个OAT文件的时候创建并且保存在文件[email protected]@[email protected]中,以后只要系统启动类路径中的DEX文件不发生变化(即不发生更新升级),那么以后每次系统启动只需要将文件[email protected]@[email protected]直接映射到内存即可,省去了创建各个类对象的时间。之前使用Dalvik虚拟机作为应用程序运行时,每次系统启动时,都需要为那些预加载的类创建类对象。因此,虽然ART运行时第一次启动时会比较慢,但是以后启动实际上会更快。

Zygote Space和Allocation Space与Dalvik虚拟机垃圾收集机制中的Zygote堆和Active堆的作用是一样的。Zygote Space在Zygote进程和应用程序进程之间共享的,而Allocation Space则是每个进程独占的。同样的,Zygote 进程一开始只有一个Image Space和一个Zygote Space。在Zygote进程fork第一个子进程之前,就会吧Zygote Space一分为二,原来的已经被使用的部分堆还叫Zygote Space,而未使用的那部分堆就叫Allocation Space。以后的对象都在Allocation Space上分配。Large Object Space就是一些离散地址的集合,用来分配一些大对象,比如分配图片。

通过上述这种方式,就可以使得Image Space和Zygote Space在Zygote进程和应用程序进程之间进行共享,而Allocation Space就每个进程都独立地拥有一份。注意,虽然Image Space和Zygote Space都是在Zygote进程和应用程序进程之间进行共享,但是前者的对象只创建一次,而后者的对象需要在系统每次启动时根据运行情况都重新创建一遍。

ART垃圾收集过程

Android ART虚拟机

ART运行的每一个Space都有不同的回收策略,ART运行时根据这个特性提供了Mark Sweep、Partial Mark Sweep和Sticky Mark Sweep等三种回收力度不同的垃圾收集器。其中,Mark Sweep的垃圾回收力度最大,它会同时回收Zygote Space、Allocation Space和Large Object Space的垃圾,Partial MarkSweep的垃圾回收力度居中,它只会同时回收Allocation Space 和Large Object Space的垃圾,而Sticky Mark Sweep的垃圾回收力度最小,它只会回收Allocation Stack的垃圾,即上次GC以后分配出来的又不再使用了的对象。力度越大的垃圾收集器,回收垃圾时需要的时候也就越长。这样我们就可以在应用程序运行的过程中根据不同的情景使用不同的垃圾收集器,那就可以更有效地执行垃圾回收过程。另外在Android 5.0以后的ART中,还引入了Semi-Space(SS)GC和Generaltional Semi-Space(GSS)、Mark-Compact(Mc)GC的三个Compaction GC,可以有效地解决内存碎片问题。

Semi-Space(SS)GC和Generational Semi-Space(GSS)GC是ART运行时引进的两个Compacting GC。它们的共同点都具有一个From Space和一个To Space。在GC执行期间,在From Space分配的还存活的对象会被一次拷贝到To Space中,这样就可以达到消除内存碎片的目的。

与SS GC相比,GSS GC 还多了一个Promote Space。当一个对象是在上一次GC之前分配的,并且在当前GC中仍然是存活的,那么它就会被拷贝到Promote Space中,而不是To Space中。这相当于是简单地将对象划分为新生代和老生代的,即在上一次GC之前分配的对象属于老生代的,而在上一次GC之后分配的对象属于新生代的。一般来说,老生代对象的存活性要比新生代的久,因此将它们拷贝到Promote Space中去,可以避免每次执行SS GC或者GSS GC时,都需要对它们进行无用的处理。

总结来说,SS GC和GSS GC的执行过程就如下图所示:

Android ART虚拟机
Android ART虚拟机

如上图所示,Bump Pointer Space 1和Bump Pointer Space2就是我们前面说的From Space和To Space。

除了Semi-Space GC和Generational Semi-Space GC,ART运行时还引入了第三种Compacting GC:Mark-Compact(MC) GC。这三种GC虽然都是Compacting GC,不过它们的实现方式却有很大不同。SSGC和GSS GC需两个Space来压缩内存,而MC GC只需一个Space来压缩内存。

Mark-compact GC主要是针对ART运行时正在使用的Bump Pointer Space进行压缩,如下图所示:

Android ART虚拟机

从图1可以看出,当Mark-Compact GC执行完成之后,原来位于Bump Pointer Space上的仍然存活的对象会被一次移动至原Bump Pointer Space的左侧,并且按地址从小到大紧凑地排列在一起。这个过程不需要借助于额外的Space来完成。这一点是Mark-Compact GC与Semi-Space GC、Generational Semi-Space GC的显著区别。

ART 与Dalvik比较

1、ART有很多GC方式可以选择,而Dalvik只有一种GC方式。

2、ART通过额外记录新分配的对象来支持更好的轻量级Sticky GC。

ART中新增的轻量级回收kGcTypeSticky只回收上次GC后再Allocation Space中新分配的垃圾对象,Heap 类的成员函数RecordAllocation首先是记录当前已经分配的内存字节数以及对象数,为了Sticky GC,还要接着再将新分配的对象压入到Heap类的成员变量allocation_stack_描述的Allocation Stack中去,而Dalvik虚拟机直接将新分配出来的对象记录在Live Bitmap中。如果不能成功将新分配的对象压入到Allocation Stack中,就说明上次GC以来,新分配的对象太多了,因此这时候就需要执行一个Sticky GC,将Allocation Stack里面的垃圾进行回收,然后再尝试将新分配的对象压入到Allocation Stack中,直到成功为止。

3、大内存块额外单独管理,可以提高内存分配和回收效率。

4、减少Suspend时间。

对于并行GC,在Handle Dirty Object阶段是在挂起ART运行时线程的前提下进行的,因此,如果把所有的Dirty Card 都放在Handle Dirty Object阶段处理,那么就会可能会造成应用程序停顿时间过长。于是,ART运行时就在并行Marking阶段也帮忙着处理Dirty Card,通过这种方式尽量减少在Handle Dirty Object阶段需要处理的Dirty Card,以达到减少应用程序因为GC造成的停顿时间。另外ART在垃圾回收过程中,也减少了挂起非GC线程的次数。