Android內存分配/回收的一個問題-爲何內存使用不多的時候也GC

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

Android Dalvik虛擬機分配及GC

首先看一下虛擬機的配置參數的意義,上面只講述了dalvik.vm.heapstartsize,是最大內存申請尺寸,app

  • 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的一個重要指標,請看如下場景:ui

場景一:當前softLimit=158M,liveSize = 150M,若是這個時候,須要分配一個100K內存的對象spa

因爲當前的上限是158M,內存是能夠直接分配成功的,分配以後,因爲空閒內存8-100K>512k,也不須要調整內存,這個時候,不存在GC,3d

Dalvik虛擬的內存分配策略--足夠.jpg
Dalvik虛擬的內存分配策略--足夠.jpg

場景二:當前softLimit=158M,liveSize = 150M,若是這個時候,須要分配的內存是7.7Mcode

因爲當前的上限是158M,內存是能夠直接分配成功的,分配以後,因爲空閒內存8-7.7M < 512k,那就須要GC,同時調整softLimitcdn

Dalvik虛擬的內存分配策略--不夠.jpg
Dalvik虛擬的內存分配策略--不夠.jpg

場景三:當前softLimit=158M,liveSize = 150M,若是這個時候,須要分配的內存是10M

因爲當前的上限是158M,內存分配失敗,須要先GC,GC以後調整softLimit,再次請求分配,若是仍是失敗,將softLimit調整爲最大,再次請求分配,失敗就再GC一次軟引用,再次請求,仍是失敗那就是OOM,成功後要調整softLimit

Dalvik虛擬的內存分配策略--不夠GC.jpg
Dalvik虛擬的內存分配策略--不夠GC.jpg

因此,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
僅供參考,歡迎指正

相關文章
相關標籤/搜索