Android應(yīng)用建立在Java虛擬機之上的,Google為了保證同時多個APP運行并及時喚醒,就為每個虛擬機設(shè)置了最大可使用內(nèi)存,通過adb命令可以查看相應(yīng)的幾個參數(shù),
* [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是最大可以使用的內(nèi)存,這個數(shù)值同廠商跟版本都有關(guān)系,隨著配置的提高,都在逐漸增大,既然虛擬機能使用的最大內(nèi)存是dalvik.vm.heapsize,那么在申請內(nèi)存的時候是不是一直到最大值才會GC呢?答案肯定是否定的,從我們檢測的曲線來看,在內(nèi)存使用很低的時候,也會GC,看下圖APP運行時情況:

從上圖看到,1,2,3這三個點好像是都發(fā)生了GC,但是這個時候,APP內(nèi)存的占用并不是很高,距離最大內(nèi)存還有很遠(yuǎn),那么這個時候為什么會發(fā)生內(nèi)存GC呢,其實直觀上也比較好理解,如果一直等到最大內(nèi)存才GC,那么就會有兩個弊端:首先,內(nèi)存資源浪費,造成系統(tǒng)性能降低,其次,GC時內(nèi)存占用越大,耗時越長,應(yīng)盡量避免。那GC的時機到底是什么時候呢?是不是每次內(nèi)存塊分配的時候都會GC,這個應(yīng)該也是否定的,本文就來簡單的了解下內(nèi)存分配、GC、內(nèi)存增長等機制。
Android Dalvik虛擬機分配及GC
首先看一下虛擬機的配置參數(shù)的意義,上面只講述了dalvik.vm.heapstartsize,是最大內(nèi)存申請尺寸,
- dalvik.vm.heapgrowthlimit和dalvik.vm.heapsize都是java虛擬機的最大內(nèi)存限制,一般heapgrowthlimit< heapsize,如果在Manifest中的application標(biāo)簽中聲明android:largeHeap=“true”,APP直到heapsize才OOM,否則達(dá)到heapgrowthlimit就OOM
- dalvik.vm.heapstartsize Java堆的起始大小,指定了Davlik虛擬機在啟動的時候向系統(tǒng)申請的物理內(nèi)存的大小,后面再根據(jù)需要逐漸向系統(tǒng)申請更多的物理內(nèi)存,直到達(dá)到MAX
- dalvik.vm.heapminfree 堆最小空閑值,GC后
- dalvik.vm.heapmaxfree堆最大空閑值
- dalvik.vm.heaptargetutilization 堆目標(biāo)利用率
后面三個值用來確保每次GC之后Java堆已經(jīng)使用和空閑的內(nèi)存有一個合適的比例,這樣可以盡量地減少GC的次數(shù),堆的利用率為U,最小空閑值為MinFree字節(jié),最大空閑值為MaxFree字節(jié),假設(shè)在某一次GC之后,存活對象占用內(nèi)存的大小為LiveSize。那么這時候堆的理想大小應(yīng)該為(LiveSize / U)。但是(LiveSize / U)必須大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree),否則,就要進行調(diào)整,調(diào)整的其實是軟上限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;
}
以上就是計算公式的源碼,假設(shè)liveSize = 150M,targetUtilization=0.75,maxFree=8,minFree=512k,那么理想尺寸200M,而200M很明顯超過了150+8,那么這個時候,堆的尺寸就應(yīng)該調(diào)整到158M,這個softLimit軟上限也是下次申請內(nèi)存時候是否需要GC的一個重要指標(biāo),請看以下場景:
場景一:當(dāng)前softLimit=158M,liveSize = 150M,如果這個時候,需要分配一個100K內(nèi)存的對象
由于當(dāng)前的上限是158M,內(nèi)存是可以直接分配成功的,分配之后,由于空閑內(nèi)存8-100K>512k,也不需要調(diào)整內(nèi)存,這個時候,不存在GC,

場景二:當(dāng)前softLimit=158M,liveSize = 150M,如果這個時候,需要分配的內(nèi)存是7.7M
由于當(dāng)前的上限是158M,內(nèi)存是可以直接分配成功的,分配之后,由于空閑內(nèi)存8-7.7M < 512k,那就需要GC,同時調(diào)整softLimit

場景三:當(dāng)前softLimit=158M,liveSize = 150M,如果這個時候,需要分配的內(nèi)存是10M
由于當(dāng)前的上限是158M,內(nèi)存分配失敗,需要先GC,GC之后調(diào)整softLimit,再次請求分配,如果還是失敗,將softLimit調(diào)整為最大,再次請求分配,失敗就再GC一次軟引用,再次請求,還是失敗那就是OOM,成功后要調(diào)整softLimit

所以,Android在申請內(nèi)存的時候,可能先分配,也可能先GC,也可能不GC,這里面最關(guān)鍵的點就是內(nèi)存利用率跟Free內(nèi)存的上下限,下面簡單看源碼了解下堆內(nèi)存分配流程:
static void *tryMalloc(size_t size)
{
void *ptr;
<!--1 首次請求分配內(nèi)存-->
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
<!--2 分配失敗,GC-->
if (gDvm.gcHeap->gcRunning) {
dvmWaitForConcurrentGcToComplete();
} else {
gcForMalloc(false);
}
<!--再次分配-->
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
<!--還是分配失敗,調(diào)整softLimit再次分配-->
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
size_t newHeapSize;
<!--分配成功后要調(diào)整softLimit-->
newHeapSize = dvmHeapSourceGetIdealFootprint();
return ptr;
}
<!--還是分配失敗,GC力加強,回收soft引用,-->
gcForMalloc(true);
<!--再次請求分配,如果還是失敗,那就OOM了-->
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
dvmDumpThread(dvmThreadSelf(), false); return NULL;
}
總結(jié)
本文主要說的一個問題就是,為什么不等到最大內(nèi)存在GC,以及普通GC的可能時機,當(dāng)然,對于內(nèi)存的GC是更加復(fù)雜的,不在本文的討論范圍之內(nèi),同時這個也解釋頻繁的分配大內(nèi)存會導(dǎo)致GC抖動的原因,畢竟,如果你超過了maxFree ,就一定GC,有興趣可以自行深入分析。