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
如果失败,则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满足需求,则可以直接成功
如果还是失败,则调用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调整为最大,再次请求分配
如果还是失败,说明真实使用的内存已经达到了上限,就快要OOM了,这个时候就需要GC一次软引用, 然后重新分配调整,与场景三不同是多了一步GC软引用的过程,后面的增长分配思路相似。
总结
本文主要说的一个问题就是,为什么不等到最大内存在GC,以及普通GC的可能时机,当然,对于内存的GC是更加复杂的,不在本文的讨论范围之内,同时这个也解释频繁的分配大内存会导致GC抖动的原因,毕竟,如果你超过了maxFree ,就一定GC,不GC,不调整softLimit,而softLimit也是下期GC的重要依据