Android内存分配/回收的几个问题

2017/10/12 Android

核心问题 :先GC 还是先扩展

Android应用是建立在Java虚拟机之上的,Google为了保证同时多个APP运行,并可以及时唤醒,就为每个虚拟机设置了最大可使用内存,通过adb命令可以查看相应的几个参数,

* [dalvik.vm.heapgrowthlimit]: [192m]
* [dalvik.vm.heapmaxfree]: [8m]
* [dalvik.vm.heapminfree]: [512k]
* [dalvik.vm.heapsize]: [512m]
* [dalvik.vm.heapstartsize]: [8m]
* [dalvik.vm.heaptargetutilization]: [0.75]

其中dalvik.vm.heapsize是最大可以使用的内存,这个数值同厂商跟版本都有关系,随着配置的提高,都在逐渐增大,既然虚拟机能使用的最大内存是dalvik.vm.heapsize,那么在申请内存的时候是不是一直到最大值才会GC呢?答案肯定是否定的,从我们检测的曲线来看,在内存使用很低的时候,也会GC,看下图APP运行时情况:

内存检测曲线

从上图看到,1,2,3这三个点好像是都发生了GC,但是这个时候,APP内存的占用并不是很高,距离最大内存还有很远,那么这个时候为什么会发生内存GC呢,其实直观上也比较好理解,如果一直等到最大内存才GC,那么就会有两个弊端:首先,内存资源浪费,造成系统性能降低,其次,GC时内存占用越大,耗时越长,尽量避免,那GC的时机到底是什么时候呢?是不是每次大块内存分配的时候都会GC,这个应该也是否定的,本文就来简单的了解下内存分配、GC、内存增长等机制。

Android Dalvik虚拟机参数意义

首先看一下虚拟机的配置参数的意义,上面只讲述了dalvik.vm.heapstartsize,是最大内存申请尺寸,

  • dalvik.vm.heapgrowthlimit和dalvik.vm.heapsize都是java虚拟机的最大内存限制,一般heapgrowthlimit< heapsize,如果在Manifest中的application标签中声明android:largeHeap=“true”,APP直到heapsize才OOM,否则达到heapgrowthlimit就OOM
  • dalvik.vm.heapstartsize Java堆的起始大小,指定了Davlik虚拟机在启动的时候向系统申请的物理内存的大小,后面再根据需要逐渐向系统申请更多的物理内存,直到达到MAX
  • dalvik.vm.heapminfree 堆最小空闲值,GC后
  • dalvik.vm.heapmaxfree堆最大空闲值
  • dalvik.vm.heaptargetutilization 堆目标利用率

在源码定义中如下

核心参数的意义

struct HeapSource {
    /* Target ideal heap utilization ratio; range 1..HEAP_UTILIZATION_MAX
     */
     <!--合理的利用率-->
    size_t targetUtilization;
    /* The starting heap size.
     */
    size_t startSize;
    /* The largest that the heap source as a whole is allowed to grow.
     */
    size_t maximumSize;
    /*
     * The largest size we permit the heap to grow.  This value allows
     * the user to limit the heap growth below the maximum size.  This
     * is a work around until we can dynamically set the maximum size.
     * This value can range between the starting size and the maximum
     * size but should never be set below the current footprint of the
     * heap.
     */
     <!--可以配置的大小,每个应用都可配置在start跟maximumSize之间-->
    size_t growthLimit;
    /* The desired max size of the heap source as a whole.
     */
     <!--基于利用率计算出来的-->
    size_t idealSize;
    /* The maximum number of bytes allowed to be allocated from the
     * active heap before a GC is forced.  This is used to "shrink" the
     * heap in lieu of actual compaction.
     * 
     */
     <!--触发GC所需要的上限 在强制 GC 之前允许从活动堆分配的最大字节数- ->
    size_t softLimit;
    /* Minimum number of free bytes. Used with the target utilization when
     * setting the softLimit. Never allows less bytes than this to be free
     * when the heap size is below the maximum size or growth limit.
     */
     <!--设置softLimit所参考的数据,不能低于此-->
    size_t minFree;
    /* Maximum number of free bytes. Used with the target utilization when
     * setting the softLimit. Never allows more bytes than this to be free
     * when the heap size is below the maximum size or growth limit.
     */
     <!--设置softLimit所参考的数据,不能高于此-->
    size_t maxFree;
    /* The heaps; heaps[0] is always the active heap,
     * which new objects should be allocated from.
     */
    Heap heaps[HEAP_SOURCE_MAX_HEAP_COUNT];
    /* The current number of heaps.
     */
    size_t numHeaps;
    /* True if zygote mode was active when the HeapSource was created.
     */
    bool sawZygote;
    /*
     * The base address of the virtual memory reservation.
     */
    char *heapBase;
    /*
     * The length in bytes of the virtual memory reservation.
     */
    size_t heapLength;
    /*
     * The live object bitmap.
     */
    HeapBitmap liveBits;
    /*
     * The mark bitmap.
     */
    HeapBitmap markBits;
    /*
     * Native allocations.
     */
    int32_t nativeBytesAllocated;
    size_t nativeFootprintGCWatermark;
    size_t nativeFootprintLimit;
    bool nativeNeedToRunFinalization;
    /*
     * State for the GC daemon.
     */
    bool hasGcThread;
    pthread_t gcThread;
    bool gcThreadShutdown;
    pthread_mutex_t gcThreadMutex;
    pthread_cond_t gcThreadCond;
    bool gcThreadTrimNeeded;
};

dalvik.vm.heapminfree [堆最小空闲值]、 dalvik.vm.heapmaxfree[堆最大空闲值] 、dalvik.vm.heaptargetutilization [堆目标利用率]、 三个值用来确保每次GC之后Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的次数。假如堆的利用率为U,最小空闲值为MinFree字节,最大空闲值为MaxFree字节,在某一次GC之后,存活对象占用内存的大小为LiveSize。那么这时候堆的理想大小应该为(LiveSize / U)。但是最终堆大小必须大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree),否则,就要进行调整,调整的其实是软上限softLimit,

static size_t getUtilizationTarget(const HeapSource* hs, size_t liveSize)
{
    size_t targetSize = (liveSize / hs->targetUtilization) * HEAP_UTILIZATION_MAX;

    if (targetSize > liveSize + hs->maxFree) {
        targetSize = liveSize + hs->maxFree;
    } else if (targetSize < liveSize + hs->minFree) {
        targetSize = liveSize + hs->minFree;
    }
    return targetSize;
}

**以上就是GC的时候计算下次GC softLimit的源码,假设本次GC后,liveSize = 150M,那么理想尺寸200M,但200M很明显超过了150+8,那么这个时候,如果这个时候softLimit尺寸就会被限制到158M,会释放多余内存,softLimit软上限是GC的重要指标,申请内存不超过softLimit,就不会触发GC,超出了才会。 **

启动虚拟机,分配初始内存,及加载初始配置

启动配置初始化,一开始softLimit不设置限制,所以一开始分配内存不会因为超出softLimit触发GC,但是由于申请的初始内存不够用了,分配失败后,会触发GC,从而,在GC的时候重新调整softLimit,softLimit是GC时候调整,并用于下次的GC判断,

GcHeap* dvmHeapSourceStartup(size_t startSize, size_t maximumSize,
                             size_t growthLimit)
{
     <!--确保设置的startSize maximumSize growthLimit有效-->
    if (!(startSize <= growthLimit && growthLimit <= maximumSize)) {
        ALOGE("Bad heap size parameters (start=%zd, max=%zd, limit=%zd)",
             startSize, maximumSize, growthLimit);
        return NULL;
    }
    /*
     * Allocate a contiguous region of virtual memory to subdivided
     * among the heaps managed by the garbage collector.
     */
    length = ALIGN_UP_TO_PAGE_SIZE(maximumSize);
    base = dvmAllocRegion(length, PROT_NONE, gDvm.zygote ? "dalvik-zygote" : "dalvik-heap");
    if (base == NULL) {
        return NULL;
    }
    <!--基础对象类的内存分配-->
    /* Create an unlocked dlmalloc mspace to use as
     * a heap source.
     */
    msp = createMspace(base, kInitialMorecoreStart, startSize);
    if (msp == NULL) {
        goto fail;
    }
    gcHeap = (GcHeap *)calloc(1, sizeof(*gcHeap));
    if (gcHeap == NULL) {
        LOGE_HEAP("Can't allocate heap descriptor");
        goto fail;
    }
    hs = (HeapSource *)calloc(1, sizeof(*hs));
    if (hs == NULL) {
        LOGE_HEAP("Can't allocate heap source");
        free(gcHeap);
        goto fail;
    }
    <!--初始化内存管理配置-->
    hs->targetUtilization = gDvm.heapTargetUtilization * HEAP_UTILIZATION_MAX;
    <!--调整的依据-->
    hs->minFree = gDvm.heapMinFree;
    hs->maxFree = gDvm.heapMaxFree;
    hs->startSize = startSize;
    hs->maximumSize = maximumSize;
    hs->growthLimit = growthLimit;
    hs->idealSize = startSize;
    <!--调整依据-->
    hs->softLimit = SIZE_MAX;    // no soft limit at first
    hs->numHeaps = 0;
    hs->sawZygote = gDvm.zygote;
    hs->nativeBytesAllocated = 0;
    hs->nativeFootprintGCWatermark = startSize;
    hs->nativeFootprintLimit = startSize * 2;
    hs->nativeNeedToRunFinalization = false;
    hs->hasGcThread = false;
    hs->heapBase = (char *)base;
    hs->heapLength = length;
    if (hs->maxFree > hs->maximumSize) {
      hs->maxFree = hs->maximumSize;
    }
    if (hs->minFree < CONCURRENT_START) {
      hs->minFree = CONCURRENT_START;
    } else if (hs->minFree > hs->maxFree) {
      hs->minFree = hs->maxFree;
    }
    if (!addInitialHeap(hs, msp, growthLimit)) {
        LOGE_HEAP("Can't add initial heap");
        goto fail;
    }
    if (!dvmHeapBitmapInit(&hs->liveBits, base, length, "dalvik-bitmap-1")) {
        LOGE_HEAP("Can't create liveBits");
        goto fail;
    }
    if (!dvmHeapBitmapInit(&hs->markBits, base, length, "dalvik-bitmap-2")) {
        LOGE_HEAP("Can't create markBits");
        dvmHeapBitmapDelete(&hs->liveBits);
        goto fail;
    }
    if (!allocMarkStack(&gcHeap->markContext.stack, hs->maximumSize)) {
        ALOGE("Can't create markStack");
        dvmHeapBitmapDelete(&hs->markBits);
        dvmHeapBitmapDelete(&hs->liveBits);
        goto fail;
    }
    gcHeap->markContext.bitmap = &hs->markBits;
    <!--赋值,后续GC 分配都用的到-->
    gcHeap->heapSource = hs;
    gHs = hs;
    return gcHeap;
fail:
    munmap(base, length);
    return NULL;
}

申请 mspace_calloc,也可能会失败。

 void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) {
  void* mem;
  size_t req = 0;
  mstate ms = (mstate)msp;
  if (!ok_magic(ms)) {
    USAGE_ERROR_ACTION(ms,ms);
    return 0;
  }
  if (n_elements != 0) {
    req = n_elements * elem_size;
    if (((n_elements | elem_size) & ~(size_t)0xffff) &&
        (req / n_elements != elem_size))
      req = MAX_SIZE_T; /* force downstream failure on overflow */
  }
  mem = internal_malloc(ms, req);
  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))
    memset(mem, 0, req);
  return mem;
}

内存分配策略源码分析

 heap.cpp
   static void *tryMalloc(size_t size)
	{
	    void *ptr;
	    <!--1 首次请求分配内存-->
	    ptr = dvmHeapSourceAlloc(size);
	    if (ptr != NULL) {
	        return ptr;
	    }
	    <!--2 分配失败,GC,GC 只能有一个,如果有另一个GC,则等待-->
	    if (gDvm.gcHeap->gcRunning) {
	        dvmWaitForConcurrentGcToComplete();
	    } else {
	     <!-- GC,并调整softLimit -->
	      gcForMalloc(false);
	    }
	    <!--再次分配-->
	    ptr = dvmHeapSourceAlloc(size);
	    if (ptr != NULL) {
	        return ptr;
	    }
		<!--还是失败,就需要增长 -->
	    ptr = dvmHeapSourceAllocAndGrow(size);
	    if (ptr != NULL) {
	        size_t newHeapSize;
	   		<!--分配成功后要调整softLimit-->
	        newHeapSize = dvmHeapSourceGetIdealFootprint();
	        return ptr;
	    }
		 <!--还是分配失败,GC力加强,回收soft引用,-->
	    gcForMalloc(true);
	    <!--再次请求分配,如果还是失败,那就OOM了-->
	    ptr = dvmHeapSourceAllocAndGrow(size);
	    if (ptr != NULL) {
	        return ptr;
	    }
	    dvmDumpThread(dvmThreadSelf(), false);		    return NULL;  
	    }

可以看到,整体策略是先调用dvmHeapSourceAlloc直接上来就分配,如果成功,不会触发GC, dvmHeapSourceAlloc 不准让堆增加:

 void* dvmHeapSourceAlloc(size_t n)
{
    HS_BOILERPLATE();
    HeapSource *hs = gHs;
    Heap* heap = hs2heap(hs);
    <!--如果超出softLimit,直接失败,softLimit会在zygote进程 addNewHeap的时候修改  addNewHeap 
    
        heap.concurrentStartBytes = hs->minFree - CONCURRENT_START;  //512K-128K 所以一开始 分配之后,如果剩余的内存不够了,会并发GC然后,重新设置 softLimit,一开始的MAX就会被替换成有效值了
	 -->
    if (heap->bytesAllocated + n > hs->softLimit) {
        /*
         * This allocation would push us over the soft limit; act as
         * if the heap is full.
         */
        LOGV_HEAP("softLimit of %zd.%03zdMB hit for %zd-byte allocation",
                  FRACTIONAL_MB(hs->softLimit), n);
        return NULL;
    }
    void* ptr;
    if (gDvm.lowMemoryMode) {
    <!--低内存模式 声明不直接用的内存,可以回收,用的事后,再分配,后续的版本没有什么地内促模式,早期安卓-->
        /* This is only necessary because mspace_calloc always memsets the
         * allocated memory to 0. This is bad for memory usage since it leads
         * to dirty zero pages. If low memory mode is enabled, we use
         * mspace_malloc which doesn't memset the allocated memory and madvise
         * the page aligned region back to the kernel.
         */
         低内存用的mspace_malloc,非低内存用的mspace_calloc
         在低内存模式中,如果我们调用的内存对象大小是超过一个page大小的,DVM假设对象不会马上就使用分配到的内存,因此,它就通过系统接口madvice()和MADV_DONTNEED标志,告诉内核刚刚分配出去的内存在近期内不会使用,内核可以对该内存做对应的物理页回收。如果分配出去的内存被使用,则内核会重新给它映射物理页,这样就可以做到按需分配物理内存。达到适合在内存小的设备上运行的特点。
        ptr = mspace_malloc(heap->msp, n);
        if (ptr == NULL) {
            return NULL;
        }
        uintptr_t zero_begin = (uintptr_t)ptr;
        uintptr_t zero_end = (uintptr_t)ptr + n;
        /* Calculate the page aligned region.
         */
        uintptr_t begin = ALIGN_UP_TO_PAGE_SIZE(zero_begin);
        uintptr_t end = zero_end & ~(uintptr_t)(SYSTEM_PAGE_SIZE - 1);
        /* If our allocation spans more than one page, we attempt to madvise.
         */
        if (begin < end) {
            /* madvise the page aligned region to kernel.
             */
            madvise((void*)begin, end - begin, MADV_DONTNEED);
            /* Zero the region after the page aligned region.
             */
            memset((void*)end, 0, zero_end - end);
            /* Zero out the region before the page aligned region.
             */
            zero_end = begin;
        }
        memset((void*)zero_begin, 0, zero_end - zero_begin);
    } else 
    <!--上面的低内存没意义,后面都是内存很足的-->
    {
    <!--高内存,分配,并且直接置零 可能物理内存真的不够了也会失败的-->
        ptr = mspace_calloc(heap->msp, 1, n);
        if (ptr == NULL) {
            return NULL;
        }
    }
    countAllocation(heap, ptr);
    /*
     * Check to see if a concurrent GC should be initiated.
     */
    if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
        /*
         * The garbage collector thread is already running or has yet
         * to be started.  Do nothing.
         */
        return ptr;
    }
    <!--Concurrent GC的阈值-->
    if (heap->bytesAllocated > heap->concurrentStartBytes) {
        /*
         * We have exceeded the allocation threshold.  Wake up thegarbage collector.
         * 是否超过GC 的阈值,超过触发Concurrent GC
         */
        dvmSignalCond(&gHs->gcThreadCond);
    }
    return ptr;
}

由于内存碎片的存在,即使是要分配的内存没有超出Active堆的Soft Limit,在调用函数mspace_malloc和函数mspace_calloc的时候,仍然有可能出现无法成功分配内存的情况。

场景一:某次GC后,当时liveSize = 150M,根据利用率与maxFree限制,softLimit=158M,如果下次,需要分配一个512K,甚至是6M 内存的对象,都可以直接成功,无需调整softLimit

image.png

如果失败,则GC一次,本次GC只会回收弱引用,GC的时候,会调整softLimit,之后再次调用dvmHeapSourceAlloc分配,

 static void gcForMalloc(bool clearSoftReferences)
{
    if (gDvm.allocProf.enabled) {
        Thread* self = dvmThreadSelf();
        gDvm.allocProf.gcCount++;
        if (self != NULL) {
            self->allocProf.gcCount++;
        }
    }
    /* This may adjust the soft limit as a side-effect.
     */
    const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
    dvmCollectGarbageInternal(spec);
}

dvmCollectGarbageInternal会去触发GC,如果主动申请内存,走的应该是主动GC,会阻塞 Log包含Starting a blocking GC Alloc

	I  Starting a blocking GC Alloc
	I  Starting a blocking GC Alloc
	I  Alloc young concurrent copying GC freed 3569(198KB) AllocSpace objects, 0(0B) LOS objects, 5% free, 402MB/426MB, paused 2.483ms total 53.217ms
	I  Starting a blocking GC Alloc
	I  Alloc concurrent copying GC freed 1630(88KB) AllocSpace objects, 1(200MB) LOS objects, 10% free, 202MB/226MB, paused 2.468ms total 207.313ms

上面就是个主动申请大内存造成GC的场景,在GC的时候,会重新调整softLimit:

void dvmCollectGarbageInternal(const GcSpec* spec)
{
    GcHeap *gcHeap = gDvm.gcHeap;
    u4 gcEnd = 0;
    u4 rootStart = 0 , rootEnd = 0;
    u4 dirtyStart = 0, dirtyEnd = 0;
    size_t numObjectsFreed, numBytesFreed;
    size_t currAllocated, currFootprint;
    size_t percentFree;
    int oldThreadPriority = INT_MAX;
    /* The heap lock must be held.
     */
     <!--只能有一个gc-->
    if (gcHeap->gcRunning) {
        LOGW_HEAP("Attempted recursive GC");
        return;
    }
    gcHeap->gcRunning = true;
    rootStart = dvmGetRelativeTimeMsec();
    <!--早期挂起所有线程-->
    dvmSuspendAllThreads(SUSPEND_FOR_GC);
    /*
     * If we are not marking concurrently raise the priority of the
     * thread performing the garbage collection.
     */
    if (!spec->isConcurrent) {
        oldThreadPriority = os_raiseThreadPriority();
    }
    if (gDvm.preVerify) {
        LOGV_HEAP("Verifying roots and heap before GC");
        verifyRootsAndHeap();
    }
    dvmMethodTraceGCBegin();
    /* Set up the marking context.
     */
    if (!dvmHeapBeginMarkStep(spec->isPartial)) {
        LOGE_HEAP("dvmHeapBeginMarkStep failed; aborting");
        dvmAbort();
    }
    /* Mark the set of objects that are strongly reachable from the roots.
     */
    LOGD_HEAP("Marking...");
    <!--标记?-->
    dvmHeapMarkRootSet();
    /* dvmHeapScanMarkedObjects() will build the lists of known
     * instances of the Reference classes.
     */
    <!--标记,扫描等-->
    ...
    ...
    ...
    LOGD_HEAP("Cleaning up...");
    dvmHeapFinishMarkStep();
    if (spec->isConcurrent) {
        dvmLockHeap();
    }
    LOGD_HEAP("Done.");
    /* 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.
     */
     <!--这个时候知道利用率是多少了,需要重新调整softlimt等参数,此时并不会真的调整内存,
     	    softLimit如果比当前的内存小,就设置,否则说明内存不够了,软上限可能设置成最大值,方便后续的内存分配-->
    dvmHeapSourceGrowForUtilization();
    <!--获取已分配-->
    currAllocated = dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0);
    currFootprint = dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0);
    
    
    dvmMethodTraceGCEnd();
    LOGV_HEAP("GC finished");
    gcHeap->gcRunning = false;
    ...
    ...
    //根据调整好的数值 清理引用 缩小堆 ,
    dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);
    。。。
    。。。
}

dvmHeapSourceGrowForUtilization就是开头说的,计算利用率 协同max min得出下次GC需要上限,注意一开始内存可能不够,GC后调整了softLimit,可能就够了

  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.
	     */
	     <!--GC后,调用这个函数才有意义,这个调整只发生在GC后,GC前不会-->
	    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;
	}

最终调用setIdealFootprint更新

 static void setIdealFootprint(size_t max)
{
    HS_BOILERPLATE();
    HeapSource *hs = gHs;
    size_t maximumSize = getMaximumSize(hs);
    if (max > maximumSize) {
        LOGI_HEAP("Clamp target GC heap from %zd.%03zdMB to %u.%03uMB",
                FRACTIONAL_MB(max),
                FRACTIONAL_MB(maximumSize));
        max = maximumSize;
    }
    /* Convert max into a size that applies to the active heap.
     * Old heaps will count against the ideal size.
     */
    size_t overhead = getSoftFootprint(false);
    size_t activeMax;
    if (overhead < max) {
        activeMax = max - overhead;
    } else {
        activeMax = 0;
    }
    setSoftLimit(hs, activeMax);
    hs->idealSize = max;
}
/*

如果 setSoftLimit不够就扩容,扩容到SIZE_MAX,因为当前GC后,达不到softLimit要求,或者说GC后没啥效果,

static void setSoftLimit(HeapSource *hs, size_t softLimit)
{
    /* Compare against the actual footprint, rather than the
     * max_allowed, because the heap may not have grown all the
     * way to the allowed size yet.
     */
    mspace msp = hs->heaps[0].msp;
    size_t currentHeapSize = mspace_footprint(msp);
    if (softLimit < currentHeapSize) {
        /* Don't let the heap grow any more, and impose a soft limit.
         */
        mspace_set_footprint_limit(msp, currentHeapSize);
        hs->softLimit = softLimit;
    } else {
        /* Let the heap grow to the requested max, and remove any
         * soft limit, if set.
         */
        mspace_set_footprint_limit(msp, softLimit);
        hs->softLimit = SIZE_MAX;
    }
}

扩容之后,再次申请。

场景二:上次softLimit=16M,liveSize = 15M,如果这个时候,需要分配的内存是5M,则先GC

由于当前的软上限是16M,则会分配失败,那就需要先GC,同时调整softLimit,调整后看是否可以分配成功,如果GC后,调整过的softLimit满足需求,则可以直接成功

image.png

如果还是失败,则调用dvmHeapSourceAllocAndGrow进行分配,这个时候就需要考虑Heap增长。

dvmHeapSourceAllocAndGrow,注意这个时候softLimit已经调整过一次了,

void* dvmHeapSourceAllocAndGrow(size_t n)
{
    HS_BOILERPLATE();
    HeapSource *hs = gHs;
    Heap* heap = hs2heap(hs);
    void* ptr = dvmHeapSourceAlloc(n);
    if (ptr != NULL) {
        return ptr;
    }
    size_t oldIdealSize = hs->idealSize;
<!--  如果这个时候,没达到理想利用率 -->
    if (isSoftLimited(hs)) {
    <!--说明是被软限制了,比如不能超出livesize太多-->
        /* We're soft-limited.  Try removing the soft limit to
         * see if we can allocate without actually growing.
         */
         <!--放开软限制-->
        hs->softLimit = SIZE_MAX;
        <!--再次分配-->
        ptr = dvmHeapSourceAlloc(n);
        if (ptr != NULL) {
            /* Removing the soft limit worked;  fix things up to
             * reflect the new effective ideal size.
             */
            snapIdealFootprint();
            return ptr;
        }
        // softLimit intentionally left at SIZE_MAX.
    }
    /* We're not soft-limited.  Grow the heap to satisfy the request.
     * If this call fails, no footprints will have changed.
     */
     <!--分配失败, 也可能导致失败,就增长,把限制放到最宽,本身heap也能增长,比如192到512 在GC掉软引用前,放开heapgrowthlimit  到最大largeHeap -->
    ptr = heapAllocAndGrow(hs, heap, n);
    if (ptr != NULL) {
        /* The allocation succeeded.  Fix up the ideal size to
         * reflect any footprint modifications that had to happen.
         */
         <!--如果成功,重新调整softlimit-->
        snapIdealFootprint();
    } else {
        /* We just couldn't do it.  Restore the original ideal size,
         * fixing up softLimit if necessary.
         */
         <!--失败就复原-->
        setIdealFootprint(oldIdealSize);
    }
    return ptr;
}

static void* heapAllocAndGrow(HeapSource *hs, Heap *heap, size_t n)
{
    /* Grow as much as possible, but don't let the real footprint
     * go over the absolute max.
     */
    size_t max = heap->maximumSize;
    mspace_set_footprint_limit(heap->msp, max);
    void* ptr = dvmHeapSourceAlloc(n);
    /* Shrink back down as small as possible.  Our caller may
     * readjust max_allowed to a more appropriate value.
     */
    mspace_set_footprint_limit(heap->msp,
                               mspace_footprint(heap->msp));
    return ptr;
}

放开到最大largeheap,然后,试试

场景三:当前softLimit=16M,liveSize = 15M,GC后仍不满足需要分配的内存4M

GC 再次请求分配,如果还是失败,将softLimit调整为最大,再次请求分配

image.png

如果还是失败,说明真实使用的内存已经达到了上限,就快要OOM了,这个时候就需要GC一次软引用, 然后重新分配调整,与场景三不同是多了一步GC软引用的过程,后面的增长分配思路相似。

总结:softLimit是GC的标志,GC是调整softLimit的措施

    • 一开始softLimit不限制,分配内存会直接分配,如果成功不GC
    • 分配失败,需要GC,GC时调整softLimit,同时可用内存变动,再次分配,如果成功,则返回
    • 如果一次GC后还是失败,那么需要将softLimit放开到最大,分配后重新调整softLimit
    • 如果还是失败,说明内存不足,需要再次GC掉软引用,之后再次请求分配,
    • 如果还是失败,OOM

本文主要说的一个问题就是,为什么不等到最大内存在GC,以及普通GC的可能时机,当然,对于内存的GC是更加复杂的,不在本文的讨论范围之内,同时这个也解释频繁的分配大内存会导致GC抖动的原因,毕竟,如果你超过了maxFree ,就一定GC,不GC,不调整softLimit,而softLimit也是下期GC的重要依据

参考文档 :Android R常见GC类型与问题案例 ART运行时垃圾收集(GC)过程分析

Search

    Table of Contents