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所需要的上限-->
    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-->
        if (gDvm.gcHeap->gcRunning) {
            dvmWaitForConcurrentGcToComplete();
        } else {
          gcForMalloc(false);
             <!-- GC,并调整softLimit -->
        }
        <!--再次分配-->
        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,

 void* dvmHeapSourceAlloc(size_t n)
{
    HS_BOILERPLATE();
    HeapSource *hs = gHs;
    Heap* heap = hs2heap(hs);
    <!--如果超出softLimit,直接失败,初次不会走这里,因为初始化的时候SIZE_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.
         */
        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;
    }
    if (heap->bytesAllocated > heap->concurrentStartBytes) {
        /*
         * We have exceeded the allocation threshold.  Wake up the
         * garbage collector.
         */
        dvmSignalCond(&gHs->gcThreadCond);
    }
    return ptr;
}

场景一:某次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,会阻塞

    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等参数,此时并不会真的调整内存,-->
    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需要上限,

  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.
     */
     <!--分配失败,就增长,把限制放到最宽-->
    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;
}

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

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

image.png

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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容