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 {
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引用,-->
gcForMalloc(true);
<!--再次請求分配,如果還是失敗,那就OOM了-->
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
dvmDumpThread(dvmThreadSelf(), false); return NULL;
}複製程式碼
總結
本文主要說的一個問題就是,為什麼不等到最大記憶體在GC,以及普通GC的可能時機,當然,對於記憶體的GC是更加複雜的,不在本文的討論範圍之內,同時這個也解釋頻繁的分配大記憶體會導致GC抖動的原因,畢竟,如果你超過了maxFree ,就一定GC,有興趣可以自行深入分析。
作者:看書的小蝸牛
原文連結:Android記憶體分配/回收的一個問題-為什麼低記憶體的時候也GC
僅供參考,歡迎指正