dalvik垃圾回收

134 阅读13分钟

Dalvik虚拟机在三种情况下会触发四种类型的GC。每一种类型GC使用一个GcSpec结构体来描述,它的定义如下所示:

struct GcSpec {  
  /* If true, only the application heap is threatened. */  
  bool isPartial;  
  /* If true, the trace is run concurrently with the mutator. */  
  bool isConcurrent;  
  /* Toggles for the soft reference clearing policy. */  
  bool doPreserve;  
  /* A name for this garbage collection mode. */  
  const char *reason;  
};  

这个结构体定义在文件dalvik/vm/alloc/Heap.h中。

GcSpec结构体的各个成员变量的含义如下所示:

  • isPartial: 为true时,表示仅仅回收Active堆的垃圾;为false时,表示同时回收Active堆和Zygote堆的垃圾。
  • isConcurrent: 为true时,表示执行并行GC;为false时,表示执行非并行GC。
  • doPreserve: 为true时,表示在执行GC的过程中,不回收软引用引用的对象;为false时,表示在执行GC的过程中,回收软引用引用的对象。
    *** reason**: 一个描述性的字符串。

Davlik虚拟机定义了四种类的GC,如下所示:

/* Not enough space for an "ordinary" Object to be allocated. */  
extern const GcSpec *GC_FOR_MALLOC;  
  
/* Automatic GC triggered by exceeding a heap occupancy threshold. */  
extern const GcSpec *GC_CONCURRENT;  
  
/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */  
extern const GcSpec *GC_EXPLICIT;  
  
/* Final attempt to reclaim memory before throwing an OOM. */  
extern const GcSpec *GC_BEFORE_OOM; 

这四个全局变量声明在文件dalvik/vm/alloc/Heap.h中。

它们的含义如下所示:

  • GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。
  • GC_CONCURRENT: 表示是在已分配内存达到一定量之后触发的GC。分配成功之后触发
  • GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
  • GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

实际上,GC_FOR_MALLOC、GC_CONCURRENT和GC_BEFORE_OOM三种类型的GC都是在分配对象的过程触发的。 GC_FOR_MALLOC 和 GC_BEFORE_OOM是分配对象之前进行的。GC_CONCURRENT是分配对象成功之后执行的

在前面Dalvik虚拟机为新创建对象分配内存的过程分析一文,我们提到,Dalvik虚拟机在Java堆上分配对象的时候,在碰到分配失败的情况,会尝试调用函数gcForMalloc进行垃圾回收。

函数gcForMalloc的实现如下所示:

static void gcForMalloc(bool clearSoftReferences)  
{  
    ......  
  
    const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;  
    dvmCollectGarbageInternal(spec);  
}  

这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。

参数clearSOftRefereces表示是否要对软引用引用的对象进行回收。如果要对软引用引用的对象进行回收,那么就表明当前内存是非常紧张的了,因此,这时候执行的就是GC_BEFORE_OOM类型的GC。否则的话,执行的就是GC_FOR_MALLOC类型的GC。它们都是通过调用函数dvmCollectGarbageInternal来执行的。

当Dalvik虚拟机成功地在堆上分配一个对象之后,会检查一下当前分配的内存是否超出一个阀值,如下所示:

void* dvmHeapSourceAlloc(size_t n)    
{    
    ......  
  
    HeapSource *hs = gHs;    
    Heap* heap = hs2heap(hs);    
    if (heap->bytesAllocated + n > hs->softLimit) {    
        ......    
        return NULL;    
    }    
    void* ptr;    
    if (gDvm.lowMemoryMode) {    
        ......    
        ptr = mspace_malloc(heap->msp, n);    
        ......  
    } else {    
        ptr = mspace_calloc(heap->msp, 1, n);    
        ......   
    }    
  
    countAllocation(heap, ptr);    
    ......    
  
    if (heap->bytesAllocated > heap->concurrentStartBytes) {    
        ......    
        //触发GC_CONCURRENT类型的GC。
        dvmSignalCond(&gHs->gcThreadCond);    
    }    
    return ptr;    
}  

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

函数dvmHeapSourceAlloc成功地在Active堆上分配到一个对象之后,就会检查Active堆当前已经分配的内存(heap->bytesAllocated)是否大于预设的阀值(heap->concurrentStartBytes)。如果大于,那么就会通过条件变量gHs->gcThreadCond唤醒GC线程进行垃圾回收。预设的阀值(heap->concurrentStartBytes)是一个比指定的堆最小空闲内存小128K的数值。也就是说,当堆的空闲内不足时,就会触发GC_CONCURRENT类型的GC。

垃圾回收过程

void dvmCollectGarbageInternal(const GcSpec* spec)  
{  
    ......  
  
    if (gcHeap->gcRunning) {  
        ......  
        return;  
    }  
    ......  
  
    gcHeap->gcRunning = true;  
    ......  
  
    dvmSuspendAllThreads(SUSPEND_FOR_GC);   
    ......  
  
    if (!dvmHeapBeginMarkStep(spec->isPartial)) {  
        ......  
        dvmAbort();  
    }  
  
    dvmHeapMarkRootSet();  
    ......  
  
    if (spec->isConcurrent) {  
        ......  
        dvmClearCardTable();  
        dvmUnlockHeap();  
        dvmResumeAllThreads(SUSPEND_FOR_GC);  
        ......  
    }  
  
    dvmHeapScanMarkedObjects();  
  
    if (spec->isConcurrent) {  
        ......  
        dvmLockHeap();  
        ......  
        dvmSuspendAllThreads(SUSPEND_FOR_GC);  
        ......  
        dvmHeapReMarkRootSet();  
        ......  
        dvmHeapReScanMarkedObjects();  
    }  
  
    dvmHeapProcessReferences(&gcHeap->softReferences,  
                             spec->doPreserve == false,  
                             &gcHeap->weakReferences,  
                             &gcHeap->finalizerReferences,  
                             &gcHeap->phantomReferences);  
    ......  
  
    dvmHeapSweepSystemWeaks();  
    ......  
     
    dvmHeapSourceSwapBitmaps();  
    .......  
  
    if (spec->isConcurrent) {  
        dvmUnlockHeap();  
        dvmResumeAllThreads(SUSPEND_FOR_GC);  
        ......  
    }  
    dvmHeapSweepUnmarkedObjects(spec->isPartial, spec->isConcurrent,  
                                &numObjectsFreed, &numBytesFreed);  
    ......  
    dvmHeapFinishMarkStep();  
    if (spec->isConcurrent) {  
        dvmLockHeap();  
    }  
    ......  
  
    dvmHeapSourceGrowForUtilization();  
    ......  
  
    gcHeap->gcRunning = false;  
    ......  
  
    if (spec->isConcurrent) {  
        ......  
        dvmBroadcastCond(&gDvm.gcHeapCond);  
    }  
  
    if (!spec->isConcurrent) {  
        dvmResumeAllThreads(SUSPEND_FOR_GC);  
        ......  
    }  
  
    .......  
    dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);  
   
    ......  
}  

标记阶段  --标记根集对象

dvmHeapMarkRootSet 方法会最终调用

static void rootMarkObjectVisitor(void *addr, u4 thread, RootType type,  
                                  void *arg)  
{  
    ......  
    Object *obj = *(Object **)addr;  
    GcMarkContext *ctx = (GcMarkContext *)arg;  
    if (obj != NULL) {  
        markObjectNonNull(obj, ctx, false);  
    }  
}  
        这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。
        参数addr指向的是Java对象地址,arg指向一个描述当前GC标记上下文的GcMarkContext结构体。函数rootMarkObjectVisitor通过调用另外一个函数markObjectNonNull来标记参数addr所描述的Java对象。
        函数markObjectNonNull的实现如下所示:
[cpp] view plain copy
static void markObjectNonNull(const Object *obj, GcMarkContext *ctx,  
                              bool checkFinger)  
{  
    ......  
    if (obj < (Object *)ctx->immuneLimit) {  
        assert(isMarked(obj, ctx));  
        return;  
    }  
    if (!setAndReturnMarkBit(ctx, obj)) {  
        /* This object was not previously marked. 
         */  
        if (checkFinger && (void *)obj < ctx->finger) {  
            /* This object will need to go on the mark stack. 
             */  
            markStackPush(&ctx->stack, obj);  
        }  
    }  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

函数markObjectNonNull不仅在标记根集对象的时候会调用到,在递归标记被根集对象所引用的对象的时候也会调用到。在标记根集对象调用时,参数checkFlinger的值等于false,而在递归标记被根集对象所引用的对象时,参数checkFlinger的值等于true,并且参数ctx指向的GcMarkContext结构体的成员变量finger被设置为当前遍历过的Java对象的最大地址值。

前面提到,对于在非回收范围内的Java对象,它们一开始的时候就已经标记为存活的了,因此,函数markObjectNonNull一开始就判断一个参数obj描述的Java对象是否在非回收堆范围内。如果是的话,那么就不需要重复对其它进行标记了。

对于在回收范围内的Java对象,则需要调用函数setAndReturnMarkBit将其在Mark Bitmap中对应的位设置为1。函数setAndReturnMarkBit在将一个位设置为1之前,要返回该位的旧值。也就是说,当函数setAndReturnMarkBit的返回值等于0时,就表示一个Java对象是第一次被标记。在这种情况下,如果参数checkFlinger的值等于true,并且被遍历的Java对象的地址值小于参数ctx指向的GcMarkContext结构体的成员变量finger,那么就需要调用函数markStackPush将该对象压入Mark Stack中,以便后面可以继续对该对象进行递归遍历。

函数setAndReturnMarkBit的实现如下所示:

static long setAndReturnMarkBit(GcMarkContext *ctx, const void *obj)  
{  
    return dvmHeapBitmapSetAndReturnObjectBit(ctx->bitmap, obj);  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

从前面Dalvik虚拟机Java堆创建过程分析一文可以知道,ctx->bitmap指向的是Mark Bitmap,因此函数setAndReturnMarkBit是将参数obj描述的Java对象在Mark Bitmap中对应的位设置为1。

函数markStackPush的实现如下所示:

static void markStackPush(GcMarkStack *stack, const Object *obj)  
{  
    ......  
    *stack->top = obj;  
    ++stack->top;  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

从这里就可以看出,函数markStackPush将参数obj描述的Java对象压入到Mark Stack中。凡是在Mark Stack中的Java对象,都是需要递归遍历它们所引用的Java对象的。

这样,当函数dvmHeapMarkRootSet调用完毕,所有的根集对象在Mark Bitmap中对应的位就都被设置为1了。

标记阶段就是将根对象放入Mark Stack,并且根集对象在Mark Bitmap中对应的位就都被设置为1了。

标记阶段 --标记根集对象引用的对象

void dvmHeapScanMarkedObjects(void)  
{  
    GcMarkContext *ctx = &gDvm.gcHeap->markContext;  
  
    ......  
    dvmHeapBitmapScanWalk(ctx->bitmap, scanBitmapCallback, ctx);  
  
    ctx->finger = (void *)ULONG_MAX;  
  
    ......  
    processMarkStack(ctx);  
}  

最终调用

static void scanObject(const Object *obj, GcMarkContext *ctx)

{  
    ......  
    if (obj->clazz == gDvm.classJavaLangClass) {  
        scanClassObject(obj, ctx);  
    } else if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {  
        scanArrayObject(obj, ctx);  
    } else {  
        scanDataObject(obj, ctx);  
    }  
}  
static void scanDataObject(const Object *obj, GcMarkContext *ctx)  
{  
    ......  
    markObject((const Object *)obj->clazz, ctx);  
    scanFields(obj, ctx);  
    if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISREFERENCE)) {  
        delayReferenceReferent((Object *)obj, ctx);  
    }  
} 


static void markObject(const Object *obj, GcMarkContext *ctx)  
{  
    if (obj != NULL) {  
        markObjectNonNull(obj, ctx, true);  
    }  
}  

回到之前方法

,对于引用类型的对象,需要调用函数delayReferenceReferent对它们所引用的目标对象进行特殊处理,如下所示:

static void delayReferenceReferent(Object *obj, GcMarkContext *ctx)  
{  
    ......  
    GcHeap *gcHeap = gDvm.gcHeap;  
    size_t pendingNextOffset = gDvm.offJavaLangRefReference_pendingNext;  
    size_t referentOffset = gDvm.offJavaLangRefReference_referent;  
    Object *pending = dvmGetFieldObject(obj, pendingNextOffset);  
    Object *referent = dvmGetFieldObject(obj, referentOffset);  
    if (pending == NULL && referent != NULL && !isMarked(referent, ctx)) {  
        Object **list = NULL;  
        if (isSoftReference(obj)) {  
            list = &gcHeap->softReferences;  
        } else if (isWeakReference(obj)) {  
            list = &gcHeap->weakReferences;  
        } else if (isFinalizerReference(obj)) {  
            list = &gcHeap->finalizerReferences;  
        } else if (isPhantomReference(obj)) {  
            list = &gcHeap->phantomReferences;  
        }  
        ......  
        enqueuePendingReference(obj, list);  
    }  
}  

清理阶段

 dvmHeapSweepUnmarkedObjects

函数dvmHeapSweepUnmarkedObjects用来清除不再被引用的对象,它的实现如下所示:

void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent,  
                                 size_t *numObjects, size_t *numBytes)  
{  
    uintptr_t base[HEAP_SOURCE_MAX_HEAP_COUNT];  
    uintptr_t max[HEAP_SOURCE_MAX_HEAP_COUNT];  
    SweepContext ctx;  
    HeapBitmap *prevLive, *prevMark;  
    size_t numHeaps, numSweepHeaps;  
  
    numHeaps = dvmHeapSourceGetNumHeaps();  
    dvmHeapSourceGetRegions(base, max, numHeaps);  
    if (isPartial) {  
        assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == base[0]);  
        numSweepHeaps = 1;  
    } else {  
        numSweepHeaps = numHeaps;  
    }  
    ctx.numObjects = ctx.numBytes = 0;  
    ctx.isConcurrent = isConcurrent;  
    prevLive = dvmHeapSourceGetMarkBits();  
    prevMark = dvmHeapSourceGetLiveBits();  
    for (size_t i = 0; i < numSweepHeaps; ++i) {  
        dvmHeapBitmapSweepWalk(prevLive, prevMark, base[i], max[i],  
                               sweepBitmapCallback, &ctx);  
    }  
    *numObjects = ctx.numObjects;  
    *numBytes = ctx.numBytes;  
    if (gDvm.allocProf.enabled) {  
        gDvm.allocProf.freeCount += ctx.numObjects;  
        gDvm.allocProf.freeSize += ctx.numBytes;  
    }  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

前面提到,当当前GC执行的是部分垃圾回收时,即参数isPartial等于true时,只有Active堆的垃圾会被回收。由于Active堆总是在base[0],所以函数dvmHeapSweepUnmarkedObjects在只执行部分垃圾回收时,将变量numSweepHeaps的值设置为1,就可以保证在后面的for循环只会遍历和清除Active堆的垃圾。

此外,变量prevLive和preMark指向的分别是当前Mark Bitmap和Live Bitmap,但是由于在执行函数dvmHeapSweepUnmarkedObjects之前,Mark Bitmap和Live Bitmap已经被交换,因此,变量prevLive和preMark实际上指向的Bitmap描述的分别上次GC后仍然存活的对象和当前GC后仍然存活的对象。

准备工作完成之后,函数dvmHeapSweepUnmarkedObjects调用函数dvmHeapBitmapSweepWalk来遍历上次GC后仍存活但是当前GC后不再存活的对象。这些对象正是要被回收的对象,它们通过函数sweepBitmapCallback来回收。

函数dvmHeapSweepUnmarkedObjects的实现如下所示:

void dvmHeapBitmapSweepWalk(const HeapBitmap *liveHb, const HeapBitmap *markHb,  
                            uintptr_t base, uintptr_t max,  
                            BitmapSweepCallback *callback, void *callbackArg)  
{  
    ......  
    void *pointerBuf[4 * HB_BITS_PER_WORD];  
    void **pb = pointerBuf;  
    size_t start = HB_OFFSET_TO_INDEX(base - liveHb->base);  
    size_t end = HB_OFFSET_TO_INDEX(max - liveHb->base);  
    unsigned long *live = liveHb->bits;  
    unsigned long *mark = markHb->bits;  
    for (size_t i = start; i <= end; i++) {  
        unsigned long garbage = live[i] & ~mark[i];  
        if (UNLIKELY(garbage != 0)) {  
            unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);  
            uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + liveHb->base;  
            while (garbage != 0) {  
                int shift = CLZ(garbage);  
                garbage &= ~(highBit >> shift);  
                *pb++ = (void *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);  
            }  
            /* Make sure that there are always enough slots available */  
            /* for an entire word of 1s. */  
            if (pb >= &pointerBuf[NELEM(pointerBuf) - HB_BITS_PER_WORD]) {  
                (*callback)(pb - pointerBuf, pointerBuf, callbackArg);  
                pb = pointerBuf;  
            }  
        }  
    }  
    if (pb > pointerBuf) {  
        (*callback)(pb - pointerBuf, pointerBuf, callbackArg);  
    }  
}  

这个函数定义在文件dalvik/vm/alloc/HeapBitmap.cpp中。

函数dvmHeapSweepUnmarkedObjects的实现逻辑很简单,它在在参数liveGB描述的Bitmap中位等于1但是在参数markHb描述的Bitmap中位却等于0的对象,也就是在上次GC后仍然存活的但是在当前GC后却不存活的对象,并且将这些对象的地址收集在变量pointerBuf描述的数组中,最后调用参数callback指向的回调函数批量回收保存在该数组中的对象。

参数callback指向的回调函数为sweepBitmapCallback,它的实现如下所示:

static void sweepBitmapCallback(size_t numPtrs, void **ptrs, void *arg)  
{  
    assert(arg != NULL);  
    SweepContext *ctx = (SweepContext *)arg;  
    if (ctx->isConcurrent) {  
        dvmLockHeap();  
    }  
    ctx->numBytes += dvmHeapSourceFreeList(numPtrs, ptrs);  
    ctx->numObjects += numPtrs;  
    if (ctx->isConcurrent) {  
        dvmUnlockHeap();  
    }  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

如果当前执行的并行GC,参照前面的图1,此时Java堆是没有被锁定的,因此,函数sweepBitmapCallback在调用另外一个函数dvmHeapSourceFreeList来回收参数ptrs描述的Java堆内存时,需要先锁定堆,并且在回收完成后解锁堆。

函数dvmHeapSourceFreeList是真正进行内存回收的地方,它的实现如下所示:

size_t dvmHeapSourceFreeList(size_t numPtrs, void **ptrs)  
{  
    ......  
    Heap* heap = ptr2heap(gHs, *ptrs);  
    size_t numBytes = 0;  
    if (heap != NULL) {  
        mspace msp = heap->msp;  
        ......  
        if (heap == gHs->heaps) {  
            // Count freed objects.  
            for (size_t i = 0; i < numPtrs; i++) {  
                ......  
                countFree(heap, ptrs[i], &numBytes);  
            }  
            // Bulk free ptrs.  
            mspace_bulk_free(msp, ptrs, numPtrs);  
        } else {  
            // This is not an 'active heap'. Only do the accounting.  
            for (size_t i = 0; i < numPtrs; i++) {  
                ......  
                countFree(heap, ptrs[i], &numBytes);  
            }  
        }  
    }  
    return numBytes;  
}  

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

函数dvmHeapSourceFreeList首先调用函数计算要释放的内存所属的堆heap,即Active堆还是Zygote堆。Active堆保存在gHs->heaps指向的Heap数组的第一个元素,根据这个信息就可以知道堆heap是Active堆还是Zygote堆。

对于Active堆,函数dvmHeapSourceFreeList首先是对每一块要释放的内存调用函数countFree进行计账,然后再调用C库提供的接口mspace_build_free批量释放参数ptrs指向的一系列内存块。

对于Zygote堆,函数dvmHeapSourceFreeList仅仅是对每一块要释放的内存调用函数countFree进行计账,但是并没有真正释放对应的内存块。这是因为Zygote堆是Zygote进程和所有的应用程序进程共享。一旦某一个进程对它进行了写类型的操作,那么就会导致对应的内存块不再在Zygote进程和应用程序进程之间进行共享。这样对Zygote堆的内存块进行释放反而会增加物理内存的开销。

收尾工作。

  1. dvmHeapFinishMarkStep

函数dvmHeapFinishMarkStep用来执行GC清理工作,它的实现如下所示:

void dvmHeapFinishMarkStep()  
{  
    GcMarkContext *ctx = &gDvm.gcHeap->markContext;  
  
    /* The mark bits are now not needed. 
     */  
    dvmHeapSourceZeroMarkBitmap();  
  
    /* Clean up everything else associated with the marking process. 
     */  
    destroyMarkStack(&ctx->stack);  
  
    ctx->finger = NULL;  
}  

这个函数定义在文件dalvik/vm/alloc/MarkSweep.cpp中。

函数dvmHeapFinishMarkStep做三个清理工作。一是调用函数dvmHeapSourceZeroMarkBitmap重置Mark Bitmap;二是调用函数destroyMarkStack销毁Mark Stack;三是将递归标记对象过程中使用到的finger值设置为NULL。

  1. dvmHeapSourceGrowForUtilization
    函数dvmHeapSourceGrowForUtilization用来将堆的大小设置为理想值,它的实现如下所示:

    /*

    • Given the current contents of the active heap, increase the allowed

    • heap footprint to match the target utilization ratio. This

    • should only be called immediately after a full mark/sweep. */
      void dvmHeapSourceGrowForUtilization()
      {
      HS_BOILERPLATE();

      HeapSource hs = gHs;
      Heap
      heap = hs2heap(hs);

      /* Use the current target utilization ratio to determine the

      • ideal heap size based on the size of the live set.
      • Note that only the active heap plays any part in this.
      • Avoid letting the old heaps influence the target free size,
      • because they may be full of objects that aren't actually
      • in the working set. Just look at the allocated size of
      • the current heap. */
        size_t currentHeapUsed = heap->bytesAllocated;
        size_t targetHeapSize = getUtilizationTarget(hs, currentHeapUsed);

      /* The ideal size includes the old heaps; add overhead so that

      • it can be immediately subtracted again in setIdealFootprint().
      • If the target heap size would exceed the max, setIdealFootprint()
      • will clamp it to a legal value. */
        size_t overhead = getSoftFootprint(false);
        setIdealFootprint(targetHeapSize + overhead);

      size_t freeBytes = getAllocLimit(hs);
      if (freeBytes < CONCURRENT_MIN_FREE) {
      /* Not enough free memory to allow a concurrent GC. */
      heap->concurrentStartBytes = SIZE_MAX;
      } else {
      heap->concurrentStartBytes = freeBytes - CONCURRENT_START;
      }

      /* Mark that we need to run finalizers and update the native watermarks

      • next time we attempt to register a native allocation. */
        gHs->nativeNeedToRunFinalization = true;
        }

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

每次GC执行完成之后,都需要根据预先设置的目标堆利用率和已经分配出去的内存字节数计算得到理想的堆大小。注意,已经分配出去的内存字节数只考虑在Active堆上分配出去的字节数。得到了Active堆已经分配出去的字节数currentHeapUsed之后,就可以调用函数getUtilizationTarget来计算Active堆的理想大小targetHeapSize了。

函数getUtilizationTarget的实现如下所示:

/* 
 * Given the size of a live set, returns the ideal heap size given 
 * the current target utilization and MIN/MAX values. 
 */  
static size_t getUtilizationTarget(const HeapSource* hs, size_t liveSize)  
{  
    /* Use the current target utilization ratio to determine the 
     * ideal heap size based on the size of the live set. 
     */  
    size_t targetSize = (liveSize / hs->targetUtilization) * HEAP_UTILIZATION_MAX;  
  
    /* Cap the amount of free space, though, so we don't end up 
     * with, e.g., 8MB of free space when the live set size hits 8MB. 
     */  
    if (targetSize > liveSize + hs->maxFree) {  
        targetSize = liveSize + hs->maxFree;  
    } else if (targetSize < liveSize + hs->minFree) {  
        targetSize = liveSize + hs->minFree;  
    }  
    return targetSize;  
}  

这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

堆的目标利用率保存在hs->targetUtilization,它是以HEAP_UTILIZATION_MAX为基数计算出来得到的一个百分比,因此这里计算堆的理想大小时,需要乘以HEAP_UTILIZATION_MAX。

计算出来的堆理想大小targetSize要满足空闲内存不能大于预先设定的最大值(hs->maxFree)以及不能小于预先设定的最小值(hs->minFree)。否则的话,就要进行相应的调整。

回到函数dvmHeapSourceGrowForUtilization中,得到了Active堆的理想大小targetHeapSize之后,还要以参数false调用函数getSoftFootprint得到Zygote堆的大小overhead。将targetHeapSize和overhead相加,将得到的结果调用函数setIdealFootprint设置Java堆的大小。由此我们就可以知道,函数setIdealFootprint设置的整个Java堆的大小,而不是Active堆的大小,因此前面需要得到Zygote堆的大小。

函数dvmHeapSourceGrowForUtilization接下来调用函数getAllocLimit得到堆当前允许分配的最大值freeBytes,然后计算并行GC触发的条件。从前面Dalvik虚拟机为新创建对象分配内存的过程分析一文可以知道,当Active堆heap当前已经分配的大小超过heap->concurrentStartBytes时,就会触发并行GC。

计算并行GC触发条件时,需要用到CONCURRENT_MIN_FREE和CONCURRENT_START两个值,它们的定义如下所示:

/* Start a concurrent collection when free memory falls under this 
 * many bytes. 
 */  
#define CONCURRENT_START (128 << 10)  
  
/* The next GC will not be concurrent when free memory after a GC is 
 * under this many bytes. 
 */  
#define CONCURRENT_MIN_FREE (CONCURRENT_START + (128 << 10))  

这两个宏定义在文件dalvik/vm/alloc/HeapSource.cpp中。

也就是说,CONCURRENT_MIN_FREE定义为256K,而CONCURRENT_START定义为128K。

由此我们就可以知道,当Active堆允许分配的内存小于256K时,禁止执行并行GC,而当Active堆允许分配的内存大于等于256K,并且剩余的空闲内存小于128K,就会触发并行GC。

函数dvmHeapSourceGrowForUtilization最后是将gHs->nativeNeedToRunFinalization的值设置为true,这样当以后我们调用dalvik.system.VMRuntime类的成员函数registerNativeAllocation注册Native内存分配数时,就会触发java.lang.System类的静态成员函数runFinalization被调用,从而使得那些定义了成员函数finalize又即将被回收的对象执行成员函数finalize。

至此,我们就分析完成Dalvik虚拟机的垃圾收集过程了。Android 5.0之后,Dalvik虚拟机已经被ART运行时替换了。我们这里分析Dalvik虚拟机垃圾收集过程的目的,是为了能够更好地理解ART运行时的垃圾收集机制,因为两者有很多相通的东西。同时,Dalvik虚拟机的垃圾收集过程要比ART运行时的垃圾收集过程简单。因此,先学习Dalvik虚拟机的垃圾收集过程,可以使得我们循序渐进地更好学习ART运行时的垃圾收集过程。在接下来的一系列文章,我们就将开始分析ART运行时的垃圾收集机制,敬请关注!