天天看點

安卓Dalvik虛拟機 GC流程分析

GC結構體

static const GcSpec kGcForMallocSpec = {
    true,  /* isPartial */
    false,  /* isConcurrent */
    true,  /* doPreserve */
    "GC_FOR_ALLOC"
};
/* Not enough space for an "ordinary" Object to be allocated. */
const GcSpec *GC_FOR_MALLOC = &kGcForMallocSpec;

static const GcSpec kGcConcurrentSpec  = {
    true,  /* isPartial */
    true,  /* isConcurrent */
    true,  /* doPreserve */
    "GC_CONCURRENT"
};
/* Automatic GC triggered by exceeding a heap occupancy threshold. */
const GcSpec *GC_CONCURRENT = &kGcConcurrentSpec;

static const GcSpec kGcExplicitSpec = {
    false,  /* isPartial */
    true,  /* isConcurrent */
    true,  /* doPreserve */
    "GC_EXPLICIT"
};
/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
const GcSpec *GC_EXPLICIT = &kGcExplicitSpec;

static const GcSpec kGcBeforeOomSpec = {
    false,  /* isPartial */
    false,  /* isConcurrent */
    false,  /* doPreserve */
    "GC_BEFORE_OOM"
};
/* Final attempt to reclaim memory before throwing an OOM. */
const GcSpec *GC_BEFORE_OOM = &kGcBeforeOomSpec;           

gcDemonThread啟動

虛拟機啟動時會初始化gcDemonThread,等待被喚醒調用,主要執行concurrent gc。

有兩個時機:

  • 一是主動調用`dvmSignalCond(&gHs->gcThreadCond);`喚醒鎖,此處是在配置設定對象時超過concurrentStartBytes時調用;
  • 二是逾時喚醒,此時會執行trimHeaps,向系統歸還虛拟記憶體和實體記憶體。
/*
 * The garbage collection daemon.  Initiates a concurrent collection
 * when signaled.  Also periodically trims the heaps when a few seconds
 * have elapsed since the last concurrent GC.
 */
static void *gcDaemonThread(void* arg)
{
    dvmChangeStatus(NULL, THREAD_VMWAIT);
    dvmLockMutex(&gHs->gcThreadMutex);
    while (gHs->gcThreadShutdown != true) {
        bool trim = false;
        if (gHs->gcThreadTrimNeeded) {
            int result = dvmRelativeCondWait(&gHs->gcThreadCond, &gHs->gcThreadMutex, HEAP_TRIM_IDLE_TIME_MS, 0);
            if (result == ETIMEDOUT) {
                /* Timed out waiting for a GC request, schedule a heap trim. */
                trim = true;
            }
        } else {
            dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex);
        }

        if (gDvm.debuggerConnected) {
            continue;
        }

        dvmLockHeap();
        /*
         * Another thread may have started a concurrent garbage
         * collection before we were scheduled.  Check for this
         * condition before proceeding.
         */
        if (!gDvm.gcHeap->gcRunning) {
            dvmChangeStatus(NULL, THREAD_RUNNING);
            if (trim) {
                trimHeaps();
                gHs->gcThreadTrimNeeded = false;
            } else {
                dvmCollectGarbageInternal(GC_CONCURRENT);
                gHs->gcThreadTrimNeeded = true;
            }
            dvmChangeStatus(NULL, THREAD_VMWAIT);
        }
        dvmUnlockHeap();
    }
    dvmChangeStatus(NULL, THREAD_RUNNING);
    return NULL;
}           

GC流程

調用dvmCollectGarbageInternal方法,進行各種類型的GC過程。

  • concurrent gc會dvmSuspendAllThreads兩次,但每次耗時短,整體對app運作影響不大,代碼中分位了suspend A 和 suspend B。
  • malloc gc會一直dvmSuspendAllThreads,是stop the world類型GC,會造成app卡頓。
void dvmCollectGarbageInternal(const GcSpec* spec) {
    if (gcHeap->gcRunning) {
        return;
    }
    gcHeap->gcRunning = true;
    
    // GC開始時間
    rootStart = dvmGetRelativeTimeMsec();
    // 挂起除gc以外所有線程
    dvmSuspendAllThreads(SUSPEND_FOR_GC);  // Suspend A

    // If we are not marking concurrently raise the priority of the thread performing the garbage collection. 非并發gc則提高線程優先級
    if (!spec->isConcurrent) {
        oldThreadPriority = os_raiseThreadPriority();
    }

    // Verifying roots and heap before GC,檢測roots是否有效
    if (gDvm.preVerify) {
        verifyRootsAndHeap();
    }

    // 建立GcMarkStack,isPartial為true則隻回收heap[0]堆的記憶體
    dvmHeapBeginMarkStep(spec->isPartial);

    // Mark the set of objects that are strongly reachable from the roots. 搜集根節點
    dvmHeapMarkRootSet();

    // 并發gc在這裡釋放鎖,Suspend A階段完成
    if (spec->isConcurrent) {
        // Resume threads while tracing from the roots.  We unlock the heap to allow mutator threads to allocate from free space.
        dvmClearCardTable();
        dvmUnlockHeap();
        dvmResumeAllThreads(SUSPEND_FOR_GC); // Suspend A
        rootEnd = dvmGetRelativeTimeMsec();  // 階段A耗時
    }


    // Recursively mark any objects that marked objects point to strongly. If we're not collecting soft references, soft-reachable objects will also be marked.
    // 以markbits中标記的root引用開始,采用遞歸的方法把所有對象的強引用對象都在markbits裡标記上,同時将這些對象壓入GcMarkStack中
    dvmHeapScanMarkedObjects();

    // 并發gc再收集一遍,主要是cardTable這裡。cardTable:為了記錄在垃圾收集過程中對象的引用情況的,以便可以實作Concurrent GC
    if (spec->isConcurrent) {
        // Re-acquire the heap lock and perform the final thread suspension.
        dirtyStart = dvmGetRelativeTimeMsec();
        dvmLockHeap();
        dvmSuspendAllThreads(SUSPEND_FOR_GC);  // Suspend B
        dvmHeapReMarkRootSet();
        // With the exception of reference objects and weak interned strings, all gray objects should now be on dirty cards.
        if (gDvm.verifyCardTable) {
            dvmVerifyCardTable();
        }
        // Recursively mark gray objects pointed to by the roots or by heap objects dirtied during the concurrent mark. 這裡從cardTable裡周遊被标記為dirty的元素
        dvmHeapReScanMarkedObjects();
    }

    // All strongly-reachable objects have now been marked.  Process weakly-reachable objects discovered while tracing. Process reference class instances and schedule finalizations. 收集一些弱引用了;
    dvmHeapProcessReferences(&gcHeap->softReferences,
                             spec->doPreserve == false,
                             &gcHeap->weakReferences,
                             &gcHeap->finalizerReferences,
                             &gcHeap->phantomReferences);

    // Process all the internal system structures that behave like weakly-held objects. 收集内部的一些弱引用的變量,如jni的弱引用
    dvmHeapSweepSystemWeaks();
    
    // 交換liveBits和markBits,因為現在markBits儲存的是GC後的對象而liveBits還是GC以前的,是以直接交換兩者,這樣就不用再花時間去重建liveBits了
    dvmHeapSourceSwapBitmaps();
    // 用新的livebits去檢查引用是否有效
    if (gDvm.postVerify) {
        verifyRootsAndHeap();
    }

    if (spec->isConcurrent) {
        dvmUnlockHeap();
        dvmResumeAllThreads(SUSPEND_FOR_GC); 
        dirtyEnd = dvmGetRelativeTimeMsec(); // 并發回收階段Suspend B結束
    }

    // Walk through the list of objects that haven't been marked and free them.  Assumes the bitmaps have been swapped. 前面收集完成了,clear所有未标注對象。
    dvmHeapSweepUnmarkedObjects(spec->isPartial, spec->isConcurrent, &numObjectsFreed, &numBytesFreed);
    // 釋放markBits 和 GcMarkStack棧
    dvmHeapFinishMarkStep();

    if (spec->isConcurrent) {
        dvmLockHeap();
    }

    /* Now's a good time to adjust the heap size, since
     * we know what our utilization is.
     *
     * This doesn't actually resize any memory;
     * it just lets the heap grow more when necessary.
     */
    // 每次gc後,嘗試着去調整堆大小,按照已配置設定記憶體 / 堆使用率 去調整堆大小
    dvmHeapSourceGrowForUtilization();
    currAllocated = dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0);
    currFootprint = dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0);

    if (spec->isConcurrent) {
        // 喚醒所有堆的鎖
        dvmBroadcastCond(&gDvm.gcHeapCond);
    }

    // 同步的回收此處才是Suspend A結束點
    if (!spec->isConcurrent) {
        dvmResumeAllThreads(SUSPEND_FOR_GC);
        dirtyEnd = dvmGetRelativeTimeMsec(); // Suspend A
        if (oldThreadPriority != INT_MAX) {
            os_lowerThreadPriority(oldThreadPriority);
        }
    }

    // 觸發被回收對象的referenceQueue
    dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);

    gcEnd = dvmGetRelativeTimeMsec(); // 一次gc運作總耗時,pause為真正suspendAll的耗時
    // 列印日志
    percentFree = 100 - (size_t)(100.0f * (float)currAllocated / currFootprint);
    if (!spec->isConcurrent) {
        u4 markSweepTime = dirtyEnd - rootStart;
        u4 gcTime = gcEnd - rootStart;
        bool isSmall = numBytesFreed > 0 && numBytesFreed < 1024;
        ALOGD("%s freed %s%zdK, %d%% free %zdK/%zdK, paused %ums, total %ums",
             spec->reason,
             isSmall ? "<" : "",
             numBytesFreed ? MAX(numBytesFreed / 1024, 1) : 0,
             percentFree,
             currAllocated / 1024, currFootprint / 1024,
             markSweepTime, gcTime);
    } else {
        u4 rootTime = rootEnd - rootStart;
        u4 dirtyTime = dirtyEnd - dirtyStart;
        u4 gcTime = gcEnd - rootStart;
        bool isSmall = numBytesFreed > 0 && numBytesFreed < 1024;
        ALOGD("%s freed %s%zdK, %d%% free %zdK/%zdK, paused %ums+%ums, total %ums",
             spec->reason,
             isSmall ? "<" : "",
             numBytesFreed ? MAX(numBytesFreed / 1024, 1) : 0,
             percentFree,
             currAllocated / 1024, currFootprint / 1024,
             rootTime, dirtyTime, gcTime);
    }
}           

繼續閱讀