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虚拟机分配及GC
首先看一下虚拟机的配置参数的意义,上面只讲述了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 堆目标利用率
后面三个值用来确保每次GC之后Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的次数,堆的利用率为U,最小空闲值为MinFree字节,最大空闲值为MaxFree字节,假设在某一次GC之后,存活对象占用内存的大小为LiveSize。那么这时候堆的理想大小应该为(LiveSize / U)。但是(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; }
以上就是计算公式的源码,假设liveSize = 150M,targetUtilization=0.75,maxFree=8,minFree=512k,那么理想尺寸200M,而200M很明显超过了150+8,那么这个时候,堆的尺寸就应该调整到158M,这个softLimit软上限也是下次申请内存时候是否需要GC的一个重要指标,请看以下场景:
场景一:当前softLimit=158M,liveSize = 150M,如果这个时候,需要分配一个100K内存的对象
由于当前的上限是158M,内存是可以直接分配成功的,分配之后,由于空闲内存8-100K>512k,也不需要调整内存,这个时候,不存在GC,
场景二:当前softLimit=158M,liveSize = 150M,如果这个时候,需要分配的内存是7.7M
由于当前的上限是158M,内存是可以直接分配成功的,分配之后,由于空闲内存8-7.7M < 512k,那就需要GC,同时调整softLimit
场景三:当前softLimit=158M,liveSize = 150M,如果这个时候,需要分配的内存是10M
由于当前的上限是158M,内存分配失败,需要先GC,GC之后调整softLimit,再次请求分配,如果还是失败,将softLimit调整为最大,再次请求分配,失败就再GC一次软引用,再次请求,还是失败那就是OOM,成功后要调整softLimit
所以,Android在申请内存的时候,可能先分配,也可能先GC,也可能不GC,这里面最关键的点就是内存利用率跟Free内存的上下限,下面简单看源码了解下堆内存分配流程:
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 { //false 弱引用 gcForMalloc(false); } <!--再次分配--> ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } <!--还是分配失败,调整softLimit再次分配--> ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { size_t newHeapSize; <!--分配成功后要调整softLimit--> newHeapSize = dvmHeapSourceGetIdealFootprint(); return ptr; } <!--还是分配失败,GC力加强,回收soft引用,--> //true 软引用 gcForMalloc(true); <!--再次请求分配,如果还是失败,那就OOM了--> ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { return ptr; } dvmDumpThread(dvmThreadSelf(), false); return NULL; }
完整版代码 heap.cpp
static void *tryMalloc(size_t size) { void *ptr; //TODO: figure out better heuristics // There will be a lot of churn if someone allocates a bunch of // big objects in a row, and we hit the frag case each time. // A full GC for each. // Maybe we grow the heap in bigger leaps // Maybe we skip the GC if the size is large and we did one recently // (number of allocations ago) (watch for thread effects) // DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other // (or, at least, there are only 0-5 objects swept each time) <!--1 首次请求分配内存--> ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } /* * The allocation failed. If the GC is running, block until it * completes and retry. */ <!--2 分配失败,GC--> if (gDvm.gcHeap->gcRunning) { /* * The GC is concurrently tracing the heap. Release the heap * lock, wait for the GC to complete, and retrying allocating. */ dvmWaitForConcurrentGcToComplete(); } else { /* * Try a foreground GC since a concurrent GC is not currently running. */ //false 弱引用 gcForMalloc(false); } ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } /* Even that didn't work; this is an exceptional state. * Try harder, growing the heap if necessary. */ ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { size_t newHeapSize; <!--分配成功后要调整softLimit--> newHeapSize = dvmHeapSourceGetIdealFootprint(); //TODO: may want to grow a little bit more so that the amount of free // space is equal to the old free space + the utilization slop for // the new allocation. LOGI_HEAP("Grow heap (frag case) to " "%zu.%03zuMB for %zu-byte allocation", FRACTIONAL_MB(newHeapSize), size); return ptr; } /* Most allocations should have succeeded by now, so the heap * is really full, really fragmented, or the requested size is * really big. Do another GC, collecting SoftReferences this * time. The VM spec requires that all SoftReferences have * been collected and cleared before throwing an OOME. */ //TODO: wait for the finalizers from the previous GC to finish LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation", size); <!--还是分配失败,GC力加强,回收soft引用,--> //true 软引用 gcForMalloc(true); ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { return ptr; } //TODO: maybe wait for finalizers and try one last time LOGE_HEAP("Out of memory on a %zd-byte allocation.", size); //TODO: tell the HeapSource to dump its state dvmDumpThread(dvmThreadSelf(), false); return NULL; }
总结
本文主要说的一个问题就是,为什么不等到最大内存在GC,以及普通GC的可能时机,当然,对于内存的GC是更加复杂的,不在本文的讨论范围之内,同时这个也解释频繁的分配大内存会导致GC抖动的原因,毕竟,如果你超过了maxFree ,就一定GC,有兴趣可以自行深入分析。