为了保证Android系统的正常运行和应用程序的稳定性,Dalvik虚拟机的内存管理机制在整个虚拟机系统中占用非常重要的位置。此文章会解决以下五个问题:

1.内存管理机制中涉及的关键数据结构

2.内存管理机制中涉及到的关键函数

3.内存分配的算法和流程

4.当前主要的垃圾回收算法

5.垃圾回收的流程

首先来看一下,内存管理机制在dalvik虚拟机中,与其他模块的协助关系,如下图:

从图中可以看出,内存管理在dalvik虚拟机中的位置,

1.主要是在初始化的时候初始化内存管理,

2.在类加载机制中分配类对象,然后对这些类对象进行管理。

Dalvik虚拟机内存分配的底层依赖:

1.基于Doug Lea 编写的dlmalloc内存分配器

2.在内存Heap上,按照分配规则完成分配

具体分配过程如下图所示

总结以下,分为四次分配,每一次分配,所做的策略不一样。

1.第一次分配失败,GC回收机制,但是不回收软引用对象

2.第二次分配失败,会增加堆内存大小

3.第三次分配失败,GC回收机制,此时会回收软引用的对象

4.第四次分配成功,然后会返回一个指向内存区域的指针,如果分配失败,会抛出空指针异常,Dalvik暂停工作。

内存分配的关键数据结构

1.HeapSource结构体

  struct HeapSource {
        size_t targetUtilization;                   /**理想的堆利用率*/
        size_t startSize;                           /**初始堆大小*/
        size_t maximumSize;                         /**堆资源作为整体允许生长的最大大小*/
        size_t growthLimit;                         /**堆可以生长到的最大大小*/
        size_t idealSize;                           /**堆资源作为整体的理想最大值*/
        size_t softLimit;                           /**允许从活动的堆中分配的最大字节数*/
    /**<heap[0]通常是活动的堆,新的对象应该从这里分配空间*/
        Heap heaps [HEAP_SOURCE_MAX_HEAP_COUNT];
        size_t numHeaps;                            /**当前堆的数量*/
        bool sawZygote;                     /**如果HeapSource创建的时候zygote是活动的,则为真*/
        char *heapBase;                             /**内存的基地址 */
        size_t heapLength;                          /**内存字节长度*/
        HeapBitmap liveBits;                        /**存活的对象位图*/
        HeapBitmap markBits;                        /**标记位图*/
 
        /**GC后台程序的状态量.*/
        bool hasGcThread;
        pthread_t gcThread;
        bool gcThreadShutdown;
        pthread_mutex_t gcThreadMutex;
        pthread_cond_t gcThreadCond;
        bool gcThreadTrimNeeded;
    };

2.Heap结构体

  struct Heap {
        mspace msp;                          /**内存分配的源内存空间*/
        size_t maximumSize;                  /**堆可以生长到的最大大小*/
        size_t bytesAllocated;               /**从内存空间中给对象分配的内存大小*/
        size_t concurrentStartBytes;         /**并发垃圾收集开始前分配的内存大小*/
        size_t objectsAllocated;             /**当前从内存空间中分配的对象数量 */
        char *base;                          /**堆的最低地址*/
        char *limit;                         /**堆的最高地址 */
    };

结构体中的变量,有做详细说明。

内存分配的关键函数

1.DvmAllocObject函数,为内存对象分配内存空间

2.CreateMspace函数,创建一个不是锁定状态的dlmalloc内存空间作为一个堆资源

3.addNewHeap函数,添加额外的堆到堆资源中

4.allocMarkStack函数,主要实现分配内存空间

5.dvmHeapSourceAlloc函数,负责dalvik虚拟机内存分配的部分过程

内存分配的关键流程

这里用一段伪代码来体现,跟上面手写的流程差不多

    Object*dvmAllocObject(ClassObject *clazz,int flags) {
        n=get object size form class object clazz
        first try: allocate n bytes from heap                         //第一次尝试
        if first try failed {                                         //如果第一次尝试失败
            run garbage collector without collecting soft references  //第一次垃圾收集
            second try: allocate n bytes from heap                    //第二次尝试
        }
        if second try failed {                                        //如果第二次尝试失败
            third try: grow the heap and allocate n bytes from heap   //第三次尝试
        }
        if third try failed {                                         //如果第三次尝试失败
            run garbage collector with collecting soft references     //第二次垃圾收集
        fourth try: grow the hap and allocate n bytes from heap       //第四次尝试
        }
        if fourth try failed,return null pointer,dalvik vm will abort //如果第四次尝试失败
    }

看下内存分配的具体流程

主要的垃圾回收算法

1.引用垃圾收集器

早期策略
在这种方法中,堆中每一个对象都有一个引用计数。
1.当一个对象被创建了,并且指向该对象的引用被分配给了一个变量,这个对象的引用计数被置为1。当任何其他变量被赋值为对这个对象的引用时,计数加1。
2.当一个对象的引用超过了生存期或者被放置一个新的值时,对象的引用计数减1。任何引用计数为0的对象可以被当作垃圾收集。
3.当一个对象被垃圾收集的时候,它引用的任何对象计数值减1。
4.一个对象被垃圾收集后可能导致后续其他对象的垃圾收集行动。 
优点:
引用计数垃圾收集器可以很快地执行,交织在程序的运行之中。这个特性对于程序不能被长时间打断的实时环境很有利。
缺点:
坏处就是,引用计数无法检测出循环(即两个或者更多的对象互相引用)。
举例说明:
父对象有一个对于对象的引用,子对象又反过来引用父对象。这些对象永远都不能计数为0,就算它们已经无法被执行程序的根对象可触及。
还有一个坏处,每次引用计数的增加或者减少都带来额外开销。 因为引用计数方法固有的缺陷,这种技术现在已经不为人所接受。现实生活中所遇到的虚拟机更有可能在垃圾收集堆中使用追踪算法。 

2.跟踪收集器,也可以称为“标记清除算法”。

跟踪收集器追踪从根节点开始的对象引用图。
在追踪过程中遇到的对象以某种方式打上标记;要么在对象本身设置标记,要么用一个独立的位图来设置标记;当追踪结束时,未被标记的对象就知道是无法触及的,从而可以被收集。 
垃圾收集过程的两个阶段:
在标记阶段,垃圾收集器遍历引用树,标记每一个遇到的对象。
在清除阶段,未被标记的对象被释放,使用的内存被返回到正在执行的程序。在Java虚拟机中,清除步骤必须包括对象的终结。

3.压缩收集器 

Java虚拟机的垃圾收集器可能有对付堆碎块的策略。标记并清除收集器通常使用的两种策略是压缩和拷贝;这两种方法都是快速地移动对象来减少堆碎块。
压缩收集器把活动的对象越过空闲区滑动到堆的一端,在这个过程中,堆的另一端出现一个大的连续空闲区;所有被移动的对象的引用也被更新,指向新的位置。 
更新被移动的对象的引用有时候通过一个间接对象引用层可以变得更简单。
不直接引用堆中的对象,对象的引用实际上指向一个对象句柄表。对象句柄才指向堆中对象的实际位置。
当对象被移动了,只有这个句柄需要被更新为新位置。所有的程序中对这个对象的引用仍然指向这个具有新值的句柄,而句柄本身没有移动。
优缺点:
简化了消除堆碎块的工作,但每一次对象访问都带来了性能损失。 

4.拷贝收集器 

拷贝垃圾收集器把所有的活动对象移动到一个新的区域。
在拷贝的过程中它们被紧挨着布置,所以可以消除原本它们在旧区域的空隙。
原有的区域被认为都是空闲区。
优缺点:
好处是对象可以在从根对象开始的遍历过程中随着发现而被拷贝,不再有标记和清除的区分。
对象被快速拷贝到新区域,同时转向指针仍然留在原来的位置。转向指针可以让垃圾收集器发现已经被转移的对象的引用。然后垃圾收集器可以把这些引用设置为转向指针的值,所以它们现在指向对象的新位置。 
一般的拷贝收集器算法被称为“停止并拷贝”。
在这个方案中,堆被分为两个区域,任何时候都只使用其中的一个区域。对象在同一个区域中分配,直到这个区域被耗尽。此时,程序执行被中止,堆被遍历,遍历时遇到的活动对象被拷贝到另一个区域。当停止和拷贝过程结束时,程序恢复执行。内存将从新的堆区域中分配,直到它也被用尽。
那时程序将再次中止,遍历堆,活动对象又被拷贝回原来的区域。
带来的代价就是,对于指定大小的堆来说需要两倍大小的内存。因为任何时候都只能使用其中的一半。

垃圾回收的流程

小结一下:

内存管理是Android系统Dalvik虚拟机的关键部分

我们可以从内存管理机制的关键数据结构、关键函数流程,以及内存分配算法流程和内存回收的算法原理,来理解内存管理。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐