Android Dalvik虚拟机 GC流程分析

Posted baiiu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Dalvik虚拟机 GC流程分析相关的知识,希望对你有一定的参考价值。

前言

本篇继续介绍安卓dalvik虚拟机系列,介绍Dalvik虚拟技的GC流程。

GC结构体

  • dalvik/vm/alloc/Heap.h
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);
    

  • dalvik/vm/alloc/MarkSweep.cpp
/* Mark the set of root objects.
 *
 * Things we need to scan:
 * - System classes defined by root classloader
 * - For each thread:
 *   - Interpreted stack, from top to "curFrame"
 *     - Dalvik registers (args + local vars)
 *   - JNI local references
 *   - Automatic VM local references (TrackedAlloc)
 *   - Associated Thread/VMThread object
 *   - ThreadGroups (could track & start with these instead of working
 *     upward from Threads)
 *   - Exception currently being thrown, if present
 * - JNI global references
 * - Interned string table
 * - Primitive classes
 * - Special objects
 *   - gDvm.outOfMemoryObj
 * - Objects in debugger object registry
 *
 * Don't need:
 * - Native stack (for in-progress stuff in the VM)
 *   - The TrackedAlloc stuff watches all native VM references.
 */
void dvmHeapMarkRootSet()

    GcHeap *gcHeap = gDvm.gcHeap;
    dvmMarkImmuneObjects(gcHeap->markContext.immuneLimit);
    dvmVisitRoots(rootMarkObjectVisitor, &gcHeap->markContext);

Dalvik虚拟机垃圾收集(GC)过程分析


以上是关于Android Dalvik虚拟机 GC流程分析的主要内容,如果未能解决你的问题,请参考以下文章

Android Dalvik虚拟机 对象创建内存分配流程

Android Dalvik虚拟机 对象创建内存分配流程

Android Dalvik虚拟机 启动和初始化

Android Dalvik虚拟机 启动和初始化

Android 性能优化 内存优化 分析GC日志

Android 性能优化 内存优化 分析GC日志