android dalvik heap 淺析
android 系統中可以在prop中配置dalvik堆的有關設定。具體設定由如下三個屬性來控制
-dalvik.vm.heapstartsize
堆分配的初始大小,調整這個值會影響到應用的流暢性和整體ram消耗。這個值越小,系統ram消耗越慢,
但是由於初始值較小,一些較大的應用需要擴張這個堆,從而引發gc和堆調整的策略,會應用反應更慢。
相反,這個值越大系統ram消耗越快,但是程式更流暢。
-dalvik.vm.heapgrowthlimit
受控情況下的極限堆(僅僅針對dalvik堆,不包括native堆)大小,dvm heap是可增長的,但是正常情況下
dvm heap的大小是不會超過dalvik.vm.heapgrowthlimit的值(非正常情況下面會詳細說明)。這個值控制那
些受控應用的極限堆大小,如果受控的應用dvm heap size超過該值,則將引發oom(out of memory)。
-dalvik.vm.heapsize
不受控情況下的極限堆大小,這個就是堆的最大值。不管它是不是受控的。這個值會影響非受控應用的dalvik
heap size。一旦dalvik heap size超過這個值,直接引發oom。
用他們三者之間的關係做一個簡單的比喻:分配dalvik heap就好像去食堂打飯,有人飯量大,要吃三碗,有人飯量小,連一碗都吃不完。如果食堂按照三碗的標準來給每個人打飯,那絕對是鋪張浪費,所以食堂的策略就是先打一碗,湊合吃,不夠了自己再來加,設定堆大小也是一樣,先給一個合理值,湊合用,自己不夠了再跟系統要。食堂畢竟是做買賣的,如果很多人明顯吃不了那麼多,硬是一碗接著一碗。為了制止這種不合理的現象,食堂又定了一個策略,一般人就只能吃三碗。但是如果虎背熊腰的大漢確實有需要,可以吃上五碗,超過五碗就不給了(太虧本了)。
開始給一碗 對應 dalvik.vm.heapstartsize
一般人最多吃三碗 對應 dalvik.vm.heapgrowthlimit
虎背熊腰的大漢最多能吃五碗 對應 dalvik.vm.heapsize
在android開發中,如果要使用大堆。需要在manifest中指定android:largeHeap為true。這樣dvm heap最大可達dalvik.vm.heapsize。其中分配過程,可以在heap.cpp裡粗略看出一些原理:
/* Try as hard as possible to allocate some memory.
*/
static void *tryMalloc(size_t size)
{
void *ptr;
/* Don't try too hard if there's no way the allocation is
* going to succeed. We have to collect SoftReferences before
* throwing an OOME, though.
*/
if (size >= gDvm.heapGrowthLimit) {
LOGW("%zd byte allocation exceeds the %zd byte maximum heap size",
size, gDvm.heapGrowthLimit);
ptr = NULL;
goto collect_soft_refs;
}
//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)
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/*
* The allocation failed. If the GC is running, block until it
* completes and retry.
*/
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();
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
}
/*
* Another failure. Our thread was starved or there may be too
* many live objects. Try a foreground GC. This will have no
* effect if the concurrent GC is already running.
*/
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;
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
collect_soft_refs:
LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation",
size);
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;
}
這裡分為如下幾個動作
1 首先判斷一下需要申請的size是不是過大,如果申請的size超過了堆的最大限制,則轉入步驟6
2 嘗試分配,如果成功則返回,失敗則轉入步驟3
3 判斷是否gc正在進行垃圾回收,如果正在進行則等待回收完成之後,嘗試分配。如果成功則返回,失敗則轉入步驟4
4 自己啟動gc進行垃圾回收,這裡gcForMalloc的引數是false。所以不會回收軟引用,回收完成後嘗試分配,如果成功則返回,失敗則轉入步驟5
5 呼叫dvmHeapSourceAllocAndGrow嘗試分配,這個函式會擴張堆。所以heap startup的時候可以給一個比較小的初始堆,實在不夠用再呼叫它進行擴張
6 進入回收軟引用階段,這裡gcForMalloc的引數是ture,所以需要回收軟引用。然後呼叫dvmHeapSourceAllocAndGrow嘗試分配,如果失敗則丟擲OOM
如果設定了largeHeap,具體流程從解析apk開始,原始碼位於PackagePaser.java中,其中parseApplication函式負責解析apk。其中有一個小段程式碼如下:
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_largeHeap,
false)) {
ai.flags |= ApplicationInfo.FLAG_LARGE_HEAP;
}
如果解析到apk中設定了largeHeap,則在applicationinfo中新增FLAG_LARGE_HEAP標籤。之後會在ActivityThead.java中的handleBindApplication處理,這個函式非常重要,底層process fork好之後,會由這個函式把上層應用繫結過去。並且呼叫上層應用的入口點。其中處理largeHeap的程式碼如下:
if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
}
這裡經過jni呼叫,最終回來到heapsource.cpp中的dvmClearGrowthLimit函式中:
/*
* Removes any growth limits. Allows the user to allocate up to the
* maximum heap size.
*/
void dvmClearGrowthLimit()
{
HS_BOILERPLATE();
dvmLockHeap();
dvmWaitForConcurrentGcToComplete();
gHs->growthLimit = gHs->maximumSize;
size_t overhead = oldHeapOverhead(gHs, false);
gHs->heaps[0].maximumSize = gHs->maximumSize - overhead;
gHs->heaps[0].limit = gHs->heaps[0].base + gHs->heaps[0].maximumSize;
dvmUnlockHeap();
}
這裡會把HeapSource的growthLimit設定為maximumSize,說簡單點就是把growthLimit有原來dalvik.vm.heapgrowthlimit的值調整為dalvik.vm.heapsize。不過分配的時候判斷oom的依據是根據heap中的maximumSize來決定。這裡不得不說一下HeapSource的兩個堆了,heaps[]陣列中有兩個堆。簡單來講,0號堆是可用堆,是開發給上層使用的。1號堆是fork的時候從zygote程序直接複製過來的,這個是死的,不會由dvm開放給上層使用。overhead標明瞭堆中已經分配可多少(包括0號堆和1號堆)。所以上層能分配打的最大使用量為
gHs->maxmumSize - overhead。
以上只是參照原始碼的個人理解,如果有什麼錯誤的地方。歡迎指正