[原文出處-------------Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)
在前面一文中,我們分析了Dalvik虛擬機創建Java堆的過程。有了Java堆之后,Dalvik虛擬機就可以在上面為對象分配內存了。在Java堆為對象分配內存需要解決內存碎片和內存不足兩個問題。要解決內存碎片問題,就要找到一塊大小最合適的空閑內存分配給對象使用。而內存不足有可能是內存配額用完引起的,也有可能是垃圾沒有及時回收引起的,要區別對待。本文就詳細分析Dalvik虛擬機是如何解決這些問題的。
內存碎片問題其實是一個通用的問題,不單止Dalvik虛擬機在Java堆為對象分配內存時會遇到,C庫的malloc函數在分配內存時也會遇到。Android系統使用的C庫bionic使用了[Doug Lea](http://gee.cs.oswego.edu/)寫的dlmalloc內存分配器。也就是說,我們調用函數malloc的時候,使用的是dlmalloc內存分配器來分配內存。這是一個成熟的內存分配器,可以很好地解決內存碎片問題。關于dlmalloc內存分配器的設計,可以參考這篇文章:[A Memory Allocator](http://gee.cs.oswego.edu/dl/html/malloc.html)。
前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文提到,Dalvik虛擬機的Java堆的底層實現是一塊匿名共享內存,并且將其抽象為C庫的一個mspace,如圖1所示:

圖1 Dalvik虛擬機Java堆
于是,Dalvik虛擬機就很機智地利用C庫里面的dlmalloc內存分配器來解決內存碎片問題!
為了應對可能面臨的內存不足問題,Dalvik虛擬機采用一種漸進的方法來為對象分配內存,直到盡了最大努力,如圖2所示:

圖2 Dalvik虛擬機為對象分配內存的過程
接下來,我們就詳細分析這個過程,以便可以了解Dalvik虛擬機是如何解決內存不足問題的,以及分配出來的內存是如何管理的。
Dalvik虛擬機實現了一個dvmAllocObject函數。每當Dalvik虛擬機需要為對象分配內存時,就會調用函數dvmAllocObject。例如,當Dalvik虛擬機的解釋器遇到一個new指令時,它就會調用函數dvmAllocObject,如下所示:
~~~
HANDLE_OPCODE(OP_NEW_INSTANCE /*vAA, class@BBBB*/)
{
ClassObject* clazz;
Object* newObj;
EXPORT_PC();
vdst = INST_AA(inst);
ref = FETCH(1);
......
clazz = dvmDexGetResolvedClass(methodClassDex, ref);
if (clazz == NULL) {
clazz = dvmResolveClass(curMethod->clazz, ref, false);
......
}
......
newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
......
SET_REGISTER(vdst, (u4) newObj);
}
FINISH(2);
OP_END
~~~
這個代碼段定義在文件dalvik/vm/mterp/out/InterpC-portable.cpp中。
關于Dalvik虛擬機的解釋器的實現,可以參考[Dalvik虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)一文,上面這段代碼首先是找到要創建的對象的類型clazz,接著以其作為參數調用函數dvmAllocObject為要創建的對象分配內存。另外一個參數ALLOC_DONT_TRACK是告訴Dalvik虛擬機的堆管理器,要分配的對象是一個根集對象,不需要對它進行跟蹤。因為根集對象在GC時是會自動被追蹤處理的。
函數dvmAllocObject的實現如下所示:
~~~
Object* dvmAllocObject(ClassObject* clazz, int flags)
{
Object* newObj;
assert(clazz != NULL);
assert(dvmIsClassInitialized(clazz) || dvmIsClassInitializing(clazz));
/* allocate on GC heap; memory is zeroed out */
newObj = (Object*)dvmMalloc(clazz->objectSize, flags);
if (newObj != NULL) {
DVM_OBJECT_INIT(newObj, clazz);
dvmTrackAllocation(clazz, clazz->objectSize); /* notify DDMS */
}
return newObj;
}
~~~
這個函數定義在文件dalvik/vm/alloc/Alloc.cpp中。
函數dvmAllocObject調用函數dvmMalloc從Java堆中分配一塊指定大小的內存給新創建的對象使用。如果分配成功,那么接下來就先使用宏DVM_OBJECT_INIT來初始化新創建對對象的成員變量clazz,使得新創建的對象可以與某個特定的類關聯起來,接著再調用函數dvmTrackAllocation記錄當前的內存分配信息,以便通知DDMS。
函數dvmMalloc返回的只是一塊內存地址,這是沒有類型的。但是由于每一個Java對象都是從Object類繼承下來的,因此,函數dvmAllocObject可以將獲得的沒有類型的內存塊強制轉換為一個Object對象。
Object類的定義如下所示:
~~~
struct Object {
/* ptr to class object */
ClassObject* clazz;
/*
* A word containing either a "thin" lock or a "fat" monitor. See
* the comments in Sync.c for a description of its layout.
*/
u4 lock;
};
~~~
這個類定義在文件dalvik/vm/oo/Object.h中。
Object類有兩個成員變量:clazz和lock。其中,成員變量clazz的類型為ClassObject,它對應于Java層的java.lang.Class類,用來描述對象所屬的類。成員變量lock是一個鎖,正是因為有了這個成員變量,在Java層中,每一個對象都可以當鎖使用。
理解了Object類的定義之后,我們繼續分析函數dvmMalloc的實現,如下所示:
~~~
void* dvmMalloc(size_t size, int flags)
{
void *ptr;
dvmLockHeap();
/* Try as hard as possible to allocate some memory.
*/
ptr = tryMalloc(size);
if (ptr != NULL) {
/* We've got the memory.
*/
if (gDvm.allocProf.enabled) {
Thread* self = dvmThreadSelf();
gDvm.allocProf.allocCount++;
gDvm.allocProf.allocSize += size;
if (self != NULL) {
self->allocProf.allocCount++;
self->allocProf.allocSize += size;
}
}
} else {
/* The allocation failed.
*/
if (gDvm.allocProf.enabled) {
Thread* self = dvmThreadSelf();
gDvm.allocProf.failedAllocCount++;
gDvm.allocProf.failedAllocSize += size;
if (self != NULL) {
self->allocProf.failedAllocCount++;
self->allocProf.failedAllocSize += size;
}
}
}
dvmUnlockHeap();
if (ptr != NULL) {
/*
* If caller hasn't asked us not to track it, add it to the
* internal tracking list.
*/
if ((flags & ALLOC_DONT_TRACK) == 0) {
dvmAddTrackedAlloc((Object*)ptr, NULL);
}
} else {
/*
* The allocation failed; throw an OutOfMemoryError.
*/
throwOOME();
}
return ptr;
}
~~~
這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。
在Java堆分配內存前后,要對Java堆進行加鎖和解鎖,避免多個線程同時對Java堆進行操作。這分別是通過函數dvmLockHeap和dvmunlockHeap來實現的。真正執行內存分配的操作是通過調用另外一個函數tryMalloc來完成的。如果分配成功,則記錄當前線程成功分配的內存字節數和對象數等信息。否則的話,就記錄當前線程失敗分配的內存字節數和對象等信息。有了這些信息之后,我們就可以通過DDMS等工具來對應用程序的內存使用信息進行統計了。
最后,如果分配內存成功,并且參數flags的ALLOC_DONT_TRACK位設置為0,那么需要將新創建的對象增加到Dalvik虛擬機內部的一個引用表去。保存在這個內部引用表的對象在執行GC時,會添加到根集去,以便可以正確地判斷對象的存活。
另一方面,如果分配內存失敗,那么就是時候調用函數throwOOME拋出一個OOM異常了。
我們接下來繼續分析函數tryMalloc的實現,如下所示:
~~~
static void *tryMalloc(size_t size)
{
void *ptr;
......
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
if (gDvm.gcHeap->gcRunning) {
......
dvmWaitForConcurrentGcToComplete();
} else {
......
gcForMalloc(false);
}
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
......
return ptr;
}
gcForMalloc(true);
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
......
return NULL;
}
~~~
這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。
函數tryMalloc的執行流程就如圖2所示:
1. 調用函數dvmHeapSourceAlloc在Java堆上分配指定大小的內存。如果分配成功,那么就將分配得到的地址直接返回給調用者了。函數dvmHeapSourceAlloc在不改變Java堆當前大小的前提下進行內存分配,這是屬于輕量級的內存分配動作。
2. 如果上一步內存分配失敗,這時候就需要執行一次GC了。不過如果GC線程已經在運行中,即gDvm.gcHeap->gcRunning的值等于true,那么就直接調用函數dvmWaitForConcurrentGcToComplete等到GC執行完成就是了。否則的話,就需要調用函數gcForMalloc來執行一次GC了,參數false表示不要回收軟引用對象引用的對象。
3. GC執行完畢后,再次調用函數dvmHeapSourceAlloc嘗試輕量級的內存分配操作。如果分配成功,那么就將分配得到的地址直接返回給調用者了。
4. 如果上一步內存分配失敗,這時候就得考慮先將Java堆的當前大小設置為Dalvik虛擬機啟動時指定的Java堆最大值,再進行內存分配了。這是通過調用函數dvmHeapSourceAllocAndGrow來實現的。
5. 如果調用函數dvmHeapSourceAllocAndGrow分配內存成功,則直接將分配得到的地址直接返回給調用者了。
6. 如果上一步內存分配還是失敗,這時候就得出狠招了。再次調用函數gcForMalloc來執行GC。參數true表示要回收軟引用對象引用的對象。
7. GC執行完畢,再次調用函數dvmHeapSourceAllocAndGrow進行內存分配。這是最后一次努力了,成功與事都到此為止。
這里涉及到的關鍵函數有三個,分別是dvmHeapSourceAlloc、dvmHeapSourceAllocAndGrow和gcForMalloc。后面一個我們在接下來一篇文章分析Dalvik虛擬機的垃圾收集過程時再分析。現在重點分析前面兩個函數。
函數dvmHeapSourceAlloc的實現如下所示:
~~~
void* dvmHeapSourceAlloc(size_t n)
{
HS_BOILERPLATE();
HeapSource *hs = gHs;
Heap* heap = hs2heap(hs);
if (heap->bytesAllocated + n > hs->softLimit) {
......
return NULL;
}
void* ptr;
if (gDvm.lowMemoryMode) {
......
ptr = mspace_malloc(heap->msp, n);
if (ptr == NULL) {
return NULL;
}
uintptr_t zero_begin = (uintptr_t)ptr;
uintptr_t zero_end = (uintptr_t)ptr + n;
......
uintptr_t begin = ALIGN_UP_TO_PAGE_SIZE(zero_begin);
uintptr_t end = zero_end & ~(uintptr_t)(SYSTEM_PAGE_SIZE - 1);
......
if (begin < end) {
......
madvise((void*)begin, end - begin, MADV_DONTNEED);
......
memset((void*)end, 0, zero_end - end);
......
zero_end = begin;
}
memset((void*)zero_begin, 0, zero_end - zero_begin);
} else {
ptr = mspace_calloc(heap->msp, 1, n);
if (ptr == NULL) {
return NULL;
}
}
countAllocation(heap, ptr);
......
if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
......
return ptr;
}
if (heap->bytesAllocated > heap->concurrentStartBytes) {
......
dvmSignalCond(&gHs->gcThreadCond);
}
return ptr;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
從前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文可知,gHs是一個全局變量,它指向一個HeapSource結構。在這個HeapSource結構中,有一個heaps數組,其中第一個元素描述的是Active堆,第二個元素描述的是Zygote堆。
通過宏hs2heap可以獲得HeapSource結構中的Active堆,保存在本地變量heap中。宏hs2heap的實現如下所示:
~~~
#define hs2heap(hs_) (&((hs_)->heaps[0]))
~~~
這個宏定義在文件dalvik/vm/alloc/HeapSource.cpp中。
在前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文中,我們解釋了Java堆有起始大小、最大值、增長上限值、最小空閑值、最大空閑值和目標利用率等參數。在Dalvik虛擬機內部,還有一個稱為軟限制(Soft Limit)的參數。堆軟限制是一個與堆目標利率相關的參數。
Java堆的Soft Limit開始的時候設置為最大允許的整數值。但是每一次GC之后,Dalvik虛擬機會根據Active堆已經分配的內存字節數、設定的堆目標利用率和Zygote堆的大小,重新計算Soft Limit,以及別外一個稱為理想大小(Ideal Size)的值。如果此時只有一個堆,即只有Active堆沒有Zygote堆,那么Soft Limit就等于Ideal Size。如果此時有兩個堆,那么Ideal Size就等于Zygote堆的大小再加上Soft Limit值,其中Soft Limit值就是此時Active堆的大小,它是根據Active堆已經分配的內存字節數和設定的堆目標利用率計算得到的。
這個Soft Limit值到底有什么用呢?它主要是用來限制Active堆無節制地增長到最大值的,而是要根據預先設定的堆目標利用率來控制Active有節奏地增長到最大值。這樣可以更有效地使用堆內存。想象一下,如果我們一開始Active堆的大小設置為最大值,那么就很有可能造成已分配的內存分布在一個很大的范圍。這樣隨著Dalvik虛擬機不斷地運行,Active堆的內存碎片就會越來越來重。相反,如果我們施加一個Soft Limit,那可以盡量地控制已分配的內存都位于較緊湊的范圍內。這樣就可以有效地減少碎片。
回到函數dvmHeapSourceAlloc中,參數n描述的是要分配的內存大小,而heap->bytesAllocated描述的是Active堆已經的內存大小。由于函數dvmHeapSourceAlloc是不允許增長Active堆的大小的,因此當(heap->bytesAllocated + n)的值大于Active堆的Soft Limit時,就直接返回一個NULL值表示分配內存失敗。
如果要分配的內存不會超過Active堆的Soft Limit,那么就要考慮Dalivk虛擬機在啟動時是否指定了低內存模式。我們可以通過-XX:LowMemoryMode選項來讓Dalvik虛擬機運行低內存模式下。在低內存模式和非低內存模塊中,對象內存的分配方式有所不同。
在低內存模式中,Dalvik虛擬機假設對象不會馬上就使用分配到的內存,因此,它就通過系統接口madvice和MADV_DONTNEED標志告訴內核,剛剛分配出去的內存在近期內不會使用,內核可以該內存對應的物理頁回收。當分配出去的內存被使用時,內核就會重新給它映射物理頁,這樣就可以做按需分配物理內存,適合在內存小的設備上運行。這里有三點需要注意。
* 第一點是Dalvik虛擬機要求分配給對象的內存初始化為0,但是在低內存模式中,是使用函數mspace_malloc來分配內存,該函數不會將分配的內存初始化為0,因此我們需要自己去初始化這塊內存。
* 第二點是對于被系統接口madvice標記為MADV_DONTNEED的內存,是不需要我們將它初始化為0的,一來是因為這是無用功(對應的物理而可能會被內核回收),二來是因為當這些內存在真正使用時,內核在為它們映射物理頁的同時,也會同時映射的物理頁初始為0。
* 第三點是在調用系統接口madvice時,指定的內存地址以及內存大小都必須以頁大小為邊界的,但是函數mspace_malloc分配出來的內存的地址只能保證對齊到8個字節,因此,我們是有可能不能將所有分配出來的內存都通過系統接口madvice標記為MADV_DONTNEED的。這時候對于不能標記為MADV_DONTNEED的內存,就需要調用memset來將它們初始化為0。
在非低內存模式中,處理的邏輯就簡單很多了,直接使用函數mspace_calloc在Active堆上分配指定的內存大小即可,同時該函數還會將分配的內存初始化為0,正好是可以滿足Dalvik虛擬機的要求。
注意,由于內存碎片的存在,即使是要分配的內存沒有超出Active堆的Soft Limit,在調用函數mspace_malloc和函數mspace_calloc的時候,仍然有可能出現無法成功分配內存的情況。在這種情況下,都直接返回一個NULL值給調用者。
在分配成功的情況下,函數dvmHeapSourceAlloc還需要做兩件事情。
第一件事情是調用函數countAllocation來計賬,它的實現如下所示:
~~~
static void countAllocation(Heap *heap, const void *ptr)
{
assert(heap->bytesAllocated < mspace_footprint(heap->msp));
heap->bytesAllocated += mspace_usable_size(ptr) +
HEAP_SOURCE_CHUNK_OVERHEAD;
heap->objectsAllocated++;
HeapSource* hs = gDvm.gcHeap->heapSource;
dvmHeapBitmapSetObjectBit(&hs->liveBits, ptr);
assert(heap->bytesAllocated < mspace_footprint(heap->msp));
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數countAllocation要計的賬有三個:
1. 記錄Active堆當前已經分配的字節數。
2. 記錄Active堆當前已經分配的對象數。
3. 調用函數dvmHeapBitmapSetObjectBit將新分配的對象在Live Heap Bitmap上對應的位設置為1,也就是說將新創建的對象標記為是存活的。關于Live Heap Bitmap,可以參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文。
回到函數dvmHeapSourceAlloc中,它需要做的第二件事情是檢查當前Active堆已經分配的字節數是否已經大于預先設定的Concurrent GC閥值heap->concurrentStartBytes。如果大于的話,那么就需要通知GC線程執行一次Concurrent GC。當然,如果當前GC線程已經在進行垃圾回收,那么就不用通知了。當gDvm.gcHeap->gcRunning的值等于true時,就表示GC線程正在進行垃圾回收。
這樣,函數dvmHeapSourceAlloc的實現就分析完成了,接下來我們繼續分析另外一個函數dvmHeapSourceAllocAndGrow的實現,如下所示:
~~~
void* dvmHeapSourceAllocAndGrow(size_t n)
{
......
HeapSource *hs = gHs;
Heap* heap = hs2heap(hs);
void* ptr = dvmHeapSourceAlloc(n);
if (ptr != NULL) {
return ptr;
}
size_t oldIdealSize = hs->idealSize;
if (isSoftLimited(hs)) {
......
hs->softLimit = SIZE_MAX;
ptr = dvmHeapSourceAlloc(n);
if (ptr != NULL) {
......
snapIdealFootprint();
return ptr;
}
}
ptr = heapAllocAndGrow(hs, heap, n);
if (ptr != NULL) {
......
snapIdealFootprint();
} else {
......
setIdealFootprint(oldIdealSize);
}
return ptr;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數dvmHeapSourceAllocAndGrow首先是在不增加Active堆的前提下,調用我們前面分析的函數dvmHeapSourceAlloc來分配大小為n的內存。如果分配成功,那么就可以直接返回了。否則的話,繼續往前處理。
在繼續往前處理之前,先記錄一下當前Zygote堆和Active堆的大小之和oldIdealSize。這是因為后面我們可能會修改Active堆的大小。當修改了Active堆的大小,但是仍然不能成功分配大小為n的內存,那么就需要恢復之前Zygote堆和Active堆的大小。
如果Active堆設置有Soft Limit,那么函數isSoftLimited的返回值等于true。在這種情況下,先將Soft Limit去掉,再調用函數dvmHeapSourceAlloc來分配大小為n的內存。如果分配成功,那么在將分配得到的地址返回給調用者之前,需要調用函數snapIdealFootprint來修改Active堆的大小。也就是說,在去掉Active堆的Soft Limit之后,可以成功地分配到大小為n的內存,這時候就需要相應的增加Soft Limit的大小。
如果Active堆沒有設置Soft Limit,或者去掉Soft Limit之后,仍然不能成功地在Active堆上分配在大小為n的內存,那么這時候就得出大招了,它會調用函數heapAllocAndGrow將Java堆的大小設置為允許的最大值,然后再在Active堆上分配大小為n的內存。
最后,如果能成功分配到大小為n的內存,那么就調用函數snapIdealFootprint來重新設置Active堆的當前大小。否則的話,就調用函數setIdealFootprint來恢復之前Active堆的大小。這是因為雖然分配失敗,但是前面仍然做了修改Active堆大小的操作。
為了更好地理解函數dvmHeapSourceAllocAndGrow的實現,我們繼續分析一下涉及到的函數isSoftLimited、setIdealFootprint、snapIdealFootprint和heapAllocAndGrow的實現。
函數isSoftLimited的實現如下所示:
~~~
static bool isSoftLimited(const HeapSource *hs)
{
/* softLimit will be either SIZE_MAX or the limit for the
* active mspace. idealSize can be greater than softLimit
* if there is more than one heap. If there is only one
* heap, a non-SIZE_MAX softLimit should always be the same
* as idealSize.
*/
return hs->softLimit <= hs->idealSize;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
根據我們前面的分析,hs->softLimit描述的是Active堆的大小,而hs->idealSize描述的是Zygote堆和Active堆的大小之和。
當只有一個堆時,即只有Active堆時,如果設置了Soft Limit,那么它的大小總是等于Active堆的大小,即這時候hs->softLimit總是等于hs->idealSize。如果沒有設置Soft Limit,那么它的值會被設置為SIZE_MAX值,這會就會保證hs->softLimit大于hs->idealSize。也就是說,當只有一個堆時,函數isSoftLimited能正確的反映Active堆是否設置有Soft Limit。
當有兩個堆時,即Zygote堆和Active堆同時存在,那么如果設置有Soft Limit,那么它的值就總是等于Active堆的大小。由于hs->idealSize描述的是Zygote堆和Active堆的大小之和,因此就一定可以保證hs->softLimit小于等于hs->idealSize。如果沒有設置Soft Limit,即hs->softLimit的值等于SIZE_MAX,那么就一定可以保證hs->softLimit的值大于hs->idealSize的值。也就是說,當有兩個堆時,函數isSoftLimited也能正確的反映Active堆是否設置有Soft Limit。
函數setIdealFootprint的實現如下所示:
~~~
static void setIdealFootprint(size_t max)
{
HS_BOILERPLATE();
HeapSource *hs = gHs;
size_t maximumSize = getMaximumSize(hs);
if (max > maximumSize) {
LOGI_HEAP("Clamp target GC heap from %zd.%03zdMB to %u.%03uMB",
FRACTIONAL_MB(max),
FRACTIONAL_MB(maximumSize));
max = maximumSize;
}
/* Convert max into a size that applies to the active heap.
* Old heaps will count against the ideal size.
*/
size_t overhead = getSoftFootprint(false);
size_t activeMax;
if (overhead < max) {
activeMax = max - overhead;
} else {
activeMax = 0;
}
setSoftLimit(hs, activeMax);
hs->idealSize = max;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數setIdealFootprint的作用是要將Zygote堆和Active堆的大小之和設置為max。在設置之前,先檢查max值是否大于Java堆允許的最大值maximum。如果大于的話,那么就屬于將max的值修改為maximum。
接下為是以參數false來調用函數getSoftFootprint來獲得Zygote堆的大小overhead。如果max的值大于Zygote堆的大小overhead,那么從max中減去overhead,就可以得到Active堆的大小activeMax。如果max的值小于等于Zygote堆的大小overhead,那么就說明要將Active堆的大小activeMax設置為0。
最后,函數setIdealFootprint調用函數setSoftLimit設置Active堆的當前大小,并且將Zygote堆和Active堆的大小之和記錄在hs->idealSize中。
這里又涉及到兩個函數getSoftFootprint和setSoftLimit,我們同樣對它們進行分析。
函數getSoftFootprint的實現如下所示:
~~~
static size_t getSoftFootprint(bool includeActive)
{
HS_BOILERPLATE();
HeapSource *hs = gHs;
size_t ret = oldHeapOverhead(hs, false);
if (includeActive) {
ret += hs->heaps[0].bytesAllocated;
}
return ret;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數getSoftFootprint首先調用函數oldHeapOverhead獲得Zygote堆的大小ret。當參數includeActive等于true時,就表示要返回的是Zygote堆的大小再加上Active堆當前已經分配的內存字節數的值。而當參數includeActive等于false時,要返回的僅僅是Zygote堆的大小。
函數oldHeapOverhead的實現如下所示:
~~~
static size_t oldHeapOverhead(const HeapSource *hs, bool includeActive)
{
size_t footprint = 0;
size_t i;
if (includeActive) {
i = 0;
} else {
i = 1;
}
for (/* i = i */; i < hs->numHeaps; i++) {
//TODO: include size of bitmaps? If so, don't use bitsLen, listen to .max
footprint += mspace_footprint(hs->heaps[i].msp);
}
return footprint;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
從這里就可以看出,當參數includeActive等于true時,函數oldHeapOverhead返回的是Zygote堆和Active堆的大小之和,而當參數includeActive等于false時,函數oldHeapOverhead僅僅返回Zygote堆的大小。注意,hs->heaps[0]指向的是Active堆,而hs->heaps[1]指向的是Zygote堆。這一點可以參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文。
回到函數setIdealFootprint中,我們繼續分析函數setSoftLimit的實現,如下所示:
~~~
static void setSoftLimit(HeapSource *hs, size_t softLimit)
{
......
mspace msp = hs->heaps[0].msp;
size_t currentHeapSize = mspace_footprint(msp);
if (softLimit < currentHeapSize) {
......
mspace_set_footprint_limit(msp, currentHeapSize);
hs->softLimit = softLimit;
} else {
......
mspace_set_footprint_limit(msp, softLimit);
hs->softLimit = SIZE_MAX;
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數setSoftLimit首先是獲得Active堆的當前大小currentHeapSize。如果參數softLimit的值小于Active堆的當前大小currentHeapSize,那么就意味著要給Active堆設置一個Soft Limit,這時候主要就是將參數softLimit的保存在hs->softLimit中。另一方面,如果參數softLimit的值大于等于Active堆的當前大小currentHeapSize,那么就意味著要去掉Active堆的Soft Limit,并且將Active堆的大小設置為參數softLimit的值。
回到函數dvmHeapSourceAlloc中,我們繼續分析最后兩個函數snapIdealFootprint和heapAllocAndGrow的實現,
函數snapIdealFootprint的實同如下所示:
~~~
static void snapIdealFootprint()
{
HS_BOILERPLATE();
setIdealFootprint(getSoftFootprint(true));
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數snapIdealFootprint通過調用前面分析的函數getSoftFootprint和setIdealFootprint來調整Active堆的大小以及Soft Limit值。回憶一下函數dvmHeapSourceAlloc調用snapIdealFootprint的情景,分別是在修改了Active堆的Soft Limit或者將Active堆的大小設置為允許的最大值,并且成功在Active堆分配了指定大小的內存之后進行的。這樣就需要調用函數snapIdealFootprint將Active堆的大小設置為實際使用的大小(而不是允許的最大值),以及重新設置Soft Limit值。
函數heapAllocAndGrow的實現如下所示:
~~~
static void* heapAllocAndGrow(HeapSource *hs, Heap *heap, size_t n)
{
......
size_t max = heap->maximumSize;
mspace_set_footprint_limit(heap->msp, max);
void* ptr = dvmHeapSourceAlloc(n);
......
mspace_set_footprint_limit(heap->msp,
mspace_footprint(heap->msp));
return ptr;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數heapAllocAndGrow使用最激進的辦法來在參數heap描述的堆上分配內存。在我們這個情景中,參數heap描述的就是Active堆上。它首先將Active堆的大小設置為允許的最大值,接著再調用函數dvmHeapSourceAlloc在上面分配大小為n的內存。接著再通過函數mspace_footprint獲得分配了n個字節之后Active堆的大小,并且將該值設置為Active堆的當前大小限制。這就相當于是將Active堆的當前大小限制值從允許設置的最大值減少為一個剛剛合適的值。
至此,我們就分析完成了Dalvik虛擬機為新創建的對象分配內存的過程。只有充分理解了對象內存的分配過程之后,我們才能夠更好地理解對象內存的釋放過程,也就是Dalvik虛擬機的垃圾收集過程。在接下來的一篇文章中,我們就將詳細分析Dalvik虛擬機的垃圾收集過程,敬請關注!
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析