Android應用創建在Java虛擬機之上的,Google爲了保證同時多個APP運行並及時喚醒,就爲每一個虛擬機設置了最大可以使用內存,經過adb命令能夠查看相應的幾個參數,java
* [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運行時狀況:android
從上圖看到,1,2,3這三個點好像是都發生了GC,可是這個時候,APP內存的佔用並非很高,距離最大內存還有很遠,那麼這個時候爲何會發生內存GC呢,其實直觀上也比較好理解,若是一直等到最大內存才GC,那麼就會有兩個弊端:首先,內存資源浪費,形成系統性能下降,其次,GC時內存佔用越大,耗時越長,應儘可能避免。那GC的時機究竟是何時呢?是否是每次內存塊分配的時候都會GC,這個應該也是否認的,本文就來簡單的瞭解下內存分配、GC、內存增加等機制。bash
首先看一下虛擬機的配置參數的意義,上面只講述了dalvik.vm.heapstartsize,是最大內存申請尺寸,app
後面三個值用來確保每次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的一個重要指標,請看如下場景:ui
場景一:當前softLimit=158M,liveSize = 150M,若是這個時候,須要分配一個100K內存的對象spa
因爲當前的上限是158M,內存是能夠直接分配成功的,分配以後,因爲空閒內存8-100K>512k,也不須要調整內存,這個時候,不存在GC,3d
場景二:當前softLimit=158M,liveSize = 150M,若是這個時候,須要分配的內存是7.7Mcode
因爲當前的上限是158M,內存是能夠直接分配成功的,分配以後,因爲空閒內存8-7.7M < 512k,那就須要GC,同時調整softLimitcdn
場景三:當前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
僅供參考,歡迎指正