[原文出處-----------Dalvik虛擬機垃圾收集(GC)過程分析](http://blog.csdn.net/luoshengyang/article/details/41822747)
前面我們分析了Dalvik虛擬機堆的創建過程,以及Java對象在堆上的分配過程。這些知識都是理解Dalvik虛擬機垃圾收集過程的基礎。垃圾收集是一個復雜的過程,它要將那些不再被引用的對象進行回收。一方面要求Dalvik虛擬機能夠標記出哪些對象是不再被引用的。另一方面要求Dalvik虛擬機盡快地回收內存,避免應用程序長時間停頓。本文就將詳細分析Dalvik虛擬機是如何解決上述問題完成垃圾收集過程的。
Dalvik虛擬機使用Mark-Sweep算法來進行垃圾收集。顧名思義,Mark-Sweep算法就是為Mark和Sweep兩個階段進行垃圾回收。其中,Mark階段從根集(Root Set)開始,遞歸地標記出當前所有被引用的對象,而Sweep階段負責回收那些沒有被引用的對象。在分析Dalvik虛擬機使用的Mark-Sweep算法之前,我們先來了解一下什么情況下會觸發GC。
Dalvik虛擬機在三種情況下會觸發四種類型的GC。每一種類型GC使用一個GcSpec結構體來描述,它的定義如下所示:
~~~
struct GcSpec {
/* If true, only the application heap is threatened. */
bool isPartial;
/* If true, the trace is run concurrently with the mutator. */
bool isConcurrent;
/* Toggles for the soft reference clearing policy. */
bool doPreserve;
/* A name for this garbage collection mode. */
const char *reason;
};
~~~
這個結構體定義在文件dalvik/vm/alloc/Heap.h中。
GcSpec結構體的各個成員變量的含義如下所示:
* **isPartial**: 為true時,表示僅僅回收Active堆的垃圾;為false時,表示同時回收Active堆和Zygote堆的垃圾。
* **isConcurrent**: 為true時,表示執行并行GC;為false時,表示執行非并行GC。
* **doPreserve**: 為true時,表示在執行GC的過程中,不回收軟引用引用的對象;為false時,表示在執行GC的過程中,回收軟引用引用的對象。
*** reason**: 一個描述性的字符串。
Davlik虛擬機定義了四種類的GC,如下所示:
~~~
/* Not enough space for an "ordinary" Object to be allocated. */
extern const GcSpec *GC_FOR_MALLOC;
/* Automatic GC triggered by exceeding a heap occupancy threshold. */
extern const GcSpec *GC_CONCURRENT;
/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
extern const GcSpec *GC_EXPLICIT;
/* Final attempt to reclaim memory before throwing an OOM. */
extern const GcSpec *GC_BEFORE_OOM;
~~~
這四個全局變量聲明在文件dalvik/vm/alloc/Heap.h中。
它們的含義如下所示:
* **GC_FOR_MALLOC**: 表示是在堆上分配對象時內存不足觸發的GC。
* **GC_CONCURRENT**: 表示是在已分配內存達到一定量之后觸發的GC。
* **GC_EXPLICIT**: 表示是應用程序調用System.gc、VMRuntime.gc接口或者收到SIGUSR1信號時觸發的GC。
* **GC_BEFORE_OOM**: 表示是在準備拋OOM異常之前進行的最后努力而觸發的GC。
實際上,GC_FOR_MALLOC、GC_CONCURRENT和GC_BEFORE_OOM三種類型的GC都是在分配對象的過程觸發的。
在前面Dalvik虛擬機為新創建對象分配內存的過程分析一文,我們提到,Dalvik虛擬機在Java堆上分配對象的時候,在碰到分配失敗的情況,會嘗試調用函數gcForMalloc進行垃圾回收。
函數gcForMalloc的實現如下所示:
~~~
static void gcForMalloc(bool clearSoftReferences)
{
......
const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
dvmCollectGarbageInternal(spec);
}
~~~
這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。
參數clearSOftRefereces表示是否要對軟引用引用的對象進行回收。如果要對軟引用引用的對象進行回收,那么就表明當前內存是非常緊張的了,因此,這時候執行的就是GC_BEFORE_OOM類型的GC。否則的話,執行的就是GC_FOR_MALLOC類型的GC。它們都是通過調用函數dvmCollectGarbageInternal來執行的。
在前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文,我們也提到,當Dalvik虛擬機成功地在堆上分配一個對象之后,會檢查一下當前分配的內存是否超出一個閥值,如下所示:
~~~
void* dvmHeapSourceAlloc(size_t n)
{
......
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);
......
} else {
ptr = mspace_calloc(heap->msp, 1, n);
......
}
countAllocation(heap, ptr);
......
if (heap->bytesAllocated > heap->concurrentStartBytes) {
......
dvmSignalCond(&gHs->gcThreadCond);
}
return ptr;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數dvmHeapSourceAlloc成功地在Active堆上分配到一個對象之后,就會檢查Active堆當前已經分配的內存(heap->bytesAllocated)是否大于預設的閥值(heap->concurrentStartBytes)。如果大于,那么就會通過條件變量gHs->gcThreadCond喚醒GC線程進行垃圾回收。預設的閥值(heap->concurrentStartBytes)是一個比指定的堆最小空閑內存小128K的數值。也就是說,當堆的空閑內不足時,就會觸發GC_CONCURRENT類型的GC。
GC線程是Dalvik虛擬機啟動的過程中創建的,它的執行體函數是gcDaemonThread,實現如下所示:
~~~
static void *gcDaemonThread(void* arg)
{
dvmChangeStatus(NULL, THREAD_VMWAIT);
dvmLockMutex(&gHs->gcThreadMutex);
while (gHs->gcThreadShutdown != true) {
bool trim = false;
if (gHs->gcThreadTrimNeeded) {
int result = dvmRelativeCondWait(&gHs->gcThreadCond, &gHs->gcThreadMutex,
HEAP_TRIM_IDLE_TIME_MS, 0);
if (result == ETIMEDOUT) {
/* Timed out waiting for a GC request, schedule a heap trim. */
trim = true;
}
} else {
dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex);
}
......
dvmLockHeap();
if (!gDvm.gcHeap->gcRunning) {
dvmChangeStatus(NULL, THREAD_RUNNING);
if (trim) {
trimHeaps();
gHs->gcThreadTrimNeeded = false;
} else {
dvmCollectGarbageInternal(GC_CONCURRENT);
gHs->gcThreadTrimNeeded = true;
}
dvmChangeStatus(NULL, THREAD_VMWAIT);
}
dvmUnlockHeap();
}
dvmChangeStatus(NULL, THREAD_RUNNING);
return NULL;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
GC線程平時沒事的時候,就在條件變量gHs->gcThreadCond上進行等待HEAP_TRIM_IDLE_TIME_MS毫秒(5000毫秒)。如果在HEAP_TRIM_IDLE_TIME_MS毫秒內,都沒有得到執行GC的通知,那么它就調用函數trimHeaps對Java堆進行裁剪,以便可以將堆上的一些沒有使用到的內存交還給內核。函數trimHeaps的實現可以參考前面[Dalvik虛擬機Java堆創建過程分](http://blog.csdn.net/luoshengyang/article/details/41581063)析一文。否則的話,就會調用函數dvmCollectGarbageInternal進行類型為GC_CONCURRENT的GC。
注意,函數gcDaemonThread在調用函數dvmCollectGarbageInternal進行類型為GC_CONCURRENT的GC之前,會先調用函數dvmLockHeap來鎖定堆的。等到GC執行完畢,再調用函數dvmUnlockHeap來解除對堆的鎖定。這與函數gcForMalloc調用函數dvmCollectGarbageInternal進行類型為GC_FOR_MALLOC和GC_CONCURRENT的GC是一樣的。只不過,對堆進行鎖定和解鎖的操作是在調用堆棧上的函數dvmMalloc進行的,具體可以參考前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文。
當應用程序調用System.gc、VMRuntime.gc接口,或者接收到SIGUSR1信號時,最終會調用到函數dvmCollectGarbage,它的實現如下所示:
~~~
void dvmCollectGarbage()
{
if (gDvm.disableExplicitGc) {
return;
}
dvmLockHeap();
dvmWaitForConcurrentGcToComplete();
dvmCollectGarbageInternal(GC_EXPLICIT);
dvmUnlockHeap();
}
~~~
這個函數定義在文件dalvik/vm/alloc/Alloc.cpp中。
如果Davik虛擬機在啟動的時候,通過-XX:+DisableExplicitGC選項禁用了顯式GC,那么函數dvmCollectGarbage什么也不做就返回了。這意味著Dalvik虛擬機可能會不支持應用程序顯式的GC請求。
一旦Dalvik虛擬機支持顯式GC,那么函數dvmCollectGarbage就會先鎖定堆,并且等待有可能正在執行的GC_CONCURRENT類型的GC完成之后,再調用函數dvmCollectGarbageInternal進行類型為GC_EXPLICIT的GC。
以上就是三種情況下會觸發四種類型的GC。有了這個背景知識之后 ,我們接下來就開始分析Dalvik虛擬機使用的Mark-Sweep算法,整個過程如圖1所示:

圖1 Dalvik虛擬機垃圾收集過程
Dalvik虛擬機支持非并行和并行兩種GC。在圖1中,左邊是非并行GC的執行過程,而右邊是并行GC的執行過程。它們的總體流程是相似的,主要差別在于前者在執行的過程中一直是掛起非GC線程的,而后者是有條件地掛起非GC線程。
由前面的分析可以知道,無論是并行GC,還是非并行GC,它們都是通過函數dvmCollectGarbageInternal來執行的。函數dvmCollectGarbageInternal的實現如下所示:
~~~
void dvmCollectGarbageInternal(const GcSpec* spec)
{
......
if (gcHeap->gcRunning) {
......
return;
}
......
gcHeap->gcRunning = true;
......
dvmSuspendAllThreads(SUSPEND_FOR_GC);
......
if (!dvmHeapBeginMarkStep(spec->isPartial)) {
......
dvmAbort();
}
dvmHeapMarkRootSet();
......
if (spec->isConcurrent) {
......
dvmClearCardTable();
dvmUnlockHeap();
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
dvmHeapScanMarkedObjects();
if (spec->isConcurrent) {
......
dvmLockHeap();
......
dvmSuspendAllThreads(SUSPEND_FOR_GC);
......
dvmHeapReMarkRootSet();
......
dvmHeapReScanMarkedObjects();
}
dvmHeapProcessReferences(&gcHeap->softReferences,
spec->doPreserve == false,
&gcHeap->weakReferences,
&gcHeap->finalizerReferences,
&gcHeap->phantomReferences);
......
dvmHeapSweepSystemWeaks();
......
dvmHeapSourceSwapBitmaps();
.......
if (spec->isConcurrent) {
dvmUnlockHeap();
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
dvmHeapSweepUnmarkedObjects(spec->isPartial, spec->isConcurrent,
&numObjectsFreed, &numBytesFreed);
......
dvmHeapFinishMarkStep();
if (spec->isConcurrent) {
dvmLockHeap();
}
......
dvmHeapSourceGrowForUtilization();
......
gcHeap->gcRunning = false;
......
if (spec->isConcurrent) {
......
dvmBroadcastCond(&gDvm.gcHeapCond);
}
if (!spec->isConcurrent) {
dvmResumeAllThreads(SUSPEND_FOR_GC);
......
}
.......
dvmEnqueueClearedReferences(&gDvm.gcHeap->clearedReferences);
......
}
~~~
這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。
前面提到,在調用函數dvmCollectGarbageInternal之前,堆是已經被鎖定了的,因此這時候任何需要堆上分配對象的線程都會被掛起。注意,這不會影響到那些不需要在堆上分配對象的線程。
在圖1中顯示的GC流程中,除了第一步的Lock Heap和最后一步的Unlock Heap,都對應于函數dvmCollectGarbageInternal的實現,它的執行邏輯如下所示:
**第1步到第3步用于并行和非并行GC:**
1. 調用函數dvmSuspendAllThreads掛起所有的線程,以免它們干擾GC。
2. 調用函數dvmHeapBeginMarkStep初始化Mark Stack,并且設定好GC范圍。關于Mark Stack的介紹,可以參考前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文。
3. 調用函數dvmHeapMarkRootSet標記根集對象。
**第4到第6步用于并行GC:**
4. 調用函數dvmClearCardTable清理Card Table。關于Card Table的介紹,可以參考前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文。因為接下來我們將會喚醒第1步掛起的線程。并且使用這個Card Table來記錄那些在GC過程中被修改的對象。
5. 調用函數dvmUnlock解鎖堆。這個是針對調用函數dvmCollectGarbageInternal執行GC前的堆鎖定操作。
6. 調用函數dvmResumeAllThreads喚醒第1步掛起的線程。
**第7步用于并行和非并行GC:**
7. 調用函數dvmHeapScanMarkedObjects從第3步獲得的根集對象開始,歸遞標記所有被根集對象引用的對象。
**第8步到第11步用于并行GC:**
8. 調用函數dvmLockHeap重新鎖定堆。這個是針對前面第5步的操作。
9. 調用函數dvmSuspendAllThreads重新掛起所有的線程。這個是針對前面第6步的操作。
10. 調用函數dvmHeapReMarkRootSet更新根集對象。因為有可能在第4步到第6步的執行過程中,有線程創建了新的根集對象。
11. 調用函數dvmHeapReScanMarkedObjects歸遞標記那些在第4步到第6步的執行過程中被修改的對象。這些對象記錄在Card Table中。
**第12步到第14步用于并行和非并行GC:**
12. 調用函數dvmHeapProcessReferences處理那些被軟引用(Soft Reference)、弱引用(Weak Reference)和影子引用(Phantom Reference)引用的對象,以及重寫了finalize方法的對象。這些對象都是需要特殊處理的。
13. 調用函數dvmHeapSweepSystemWeaks回收系統內部使用的那些被弱引用引用的對象。
14. 調用函數dvmHeapSourceSwapBitmaps交換Live Bitmap和Mark Bitmap。執行了前面的13步之后,所有還被引用的對象在Mark Bitmap中的bit都被設置為1。而Live Bitmap記錄的是當前GC前還被引用著的對象。通過交換這兩個Bitmap,就可以使得當前GC完成之后,使得Live Bitmap記錄的是下次GC前還被引用著的對象。
**第15步和第16步用于并行GC:**
15. 調用函數dvmUnlock解鎖堆。這個是針對前面第8步的操作。
16. 調用函數dvmResumeAllThreads喚醒第9步掛起的線程。
**第17步和第18步用于并行和非并行GC:**
17. 調用函數dvmHeapSweepUnmarkedObjects回收那些沒有被引用的對象。沒有被引用的對象就是那些在執行第14步之前,在Live Bitmap中的bit設置為1,但是在Mark Bitmap中的bit設置為0的對象。
18. 調用函數dvmHeapFinishMarkStep重置Mark Bitmap以及Mark Stack。這個是針對前面第2步的操作。
**第19步用于并行GC:**
19. 調用函數dvmLockHeap重新鎖定堆。這個是針對前面第15步的操作。
**第20步用于并行和非并行GC:**
20. 調用函數dvmHeapSourceGrowForUtilization根據設置的堆目標利用率調整堆的大小。
**第21步用于并行GC:**
21. 調用函數dvmBroadcastCond喚醒那些等待GC執行完成再在堆上分配對象的線程。
**第22步用于非并行GC:**
22. 調用函數dvmResumeAllThreads喚醒第1步掛起的線程。
**第23步用到并行和非并行GC:**
23. 調用函數dvmEnqueueClearedReferences將那些目標對象已經被回收了的引用對象增加到相應的Java隊列中去,以便應用程序可以知道哪些引用引用的對象已經被回收了。
以上就是并行和非并行GC的執行總體流程,它們的主要區別在于,前者在GC過程中,有條件地掛起和喚醒非GC線程,而后者在執行GC的過程中,一直都是掛起非GC線程的。并行GC通過有條件地掛起和喚醒非GC線程,就可以使得應用程序獲得更好的響應性。但是我們也應該看到,并行GC需要多執行一次標記根集對象以及遞歸標記那些在GC過程被訪問了的對象的操作。也就是說,并行GC在使用得應用程序獲得更好的響應性的同時,也需要花費更多的CPU資源。
為了更深入地理解GC的執行過程,接下來我們再詳細分析在上述的23步中涉及到的重要函數。
1. dvmSuspendAllThreads
函數dvmSuspendAllThreads用來掛起Dalvik虛擬機中除當前線程之外的其它線程,它的實現如下所示:
~~~
void dvmSuspendAllThreads(SuspendCause why)
{
Thread* self = dvmThreadSelf();
Thread* thread;
......
lockThreadSuspend("susp-all", why);
......
dvmLockThreadList(self);
lockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
continue;
dvmAddToSuspendCounts(thread, 1,
(why == SUSPEND_FOR_DEBUG ||
why == SUSPEND_FOR_DEBUG_EVENT)
? 1 : 0);
}
unlockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
continue;
/* wait for the other thread to see the pending suspend */
waitForThreadSuspend(self, thread);
......
}
dvmUnlockThreadList();
unlockThreadSuspend();
......
}
~~~
這個函數定義在文件dalvik/vm/Thread.cpp中。
在以下三種情況下,當前線程需要掛起其它線程:
1. 執行GC。
2. 調試器請求。
3. 發生調試事件,如斷點命中。
而且,上述三種情況有可能是同時發生的。例如,一個線程在分配對象的過程中發生GC的同時,調試器也剛好請求掛起所有線程。這時候就需要保證每一個掛起操作都是順序執行的,即要對掛起線程的操作進行加鎖。這是通過調用函數函數lockThreadSuspend來實現的。
函數lockThreadSuspend嘗試獲取gDvm._threadSuspendLock鎖。如果獲取失敗,就表明有其它線程也正在請求掛起Dalvik虛擬機中的線程,包括當前線程。每一個Dalvik虛擬機線程都有一個Suspend Count計數,每當它們都掛起的時候,對應的Suspend Count計數就會加1,而當被喚醒時,對應的Suspend Count計數就會減1。在獲取gDvm._threadSuspendLock鎖失敗的情況下,當前線程按照一定的時間間隔檢查自己的Suspend Count,直到自己的Suspend Count等于0,并且能成功獲取gDvm._threadSuspendLock鎖為止。這樣就可以保證每一個掛起Dalvik虛擬機線程的請求都能夠得到順序執行。
從函數lockThreadSuspend返回之后,就表明當前線程可以執行掛起其它線程的操作了。它首先要做的第一件事情是遍歷Dalvik虛擬機線程列表,并且調用函數dvmAddToSuspendCounts將列表里面的每一個線程對應的Suspend Count都增加1,但是除了當前線程之外。注意,當掛起線程的請求是調試器發出或者是由調試事件觸發的時候,Dalvik虛擬機中的JDWP線程是不可以掛起的,因為JDWP線程掛起來之后,就沒法調試了。在這種情況下,也不能將JDWP線程對應的Suspend Count都增加1。
通過調用函數dvmJdwpGetDebugThread可以獲得JDWP線程句柄,用這個句柄和Dalvik虛擬機線程列表中的線程句柄相比,就可以知道當前遍歷的線程是否是JDWP線程。同時,通過參數why可以知道當掛起線程的請求是否是由調試器發出的或者由調試事件觸發的,
注意,在增加被掛起線程的Suspend Count計數之前,必須要獲取gDvm.threadSuspendCountLock鎖。這個鎖的獲取和釋放可以通過函數lockThreadSuspendCount和unlockThreadSuspendCount完成。
將被掛起線程的Suspend Count計數都增加1之后,接下來就是等待被掛起線程自愿將自己掛起來了。這是通過函數waitForThreadSuspend來實現。當一個線程自愿將自己掛起來的時候,會將自己的狀態設置為非運行狀態(THREAD_RUNNING),這樣函數waitForThreadSuspend通過不斷地檢查一個線程的狀態是否處于非運行狀態就可以知道它是否已經掛起來了。
那么,一個線程在什么情況才會自愿將自己掛起來呢?一個線程在執行的過程中,會在合適的時候檢查自己的Suspend Count計數。一旦該計數值不等于0,那么它就知道有線程請求掛起自己,因此它就會很配合地將自己的狀態設置為非運行的,并且將自己掛起來。例如,當一個線程通過解釋器執行代碼時,就會周期性地檢查自己的Suspend Count是否等于0。這里說的周期性,實際上就是碰到IF指令、GOTO指令、SWITCH指令、RETURN指令和THROW指令等時。
2. dvmHeapBeginMarkStep
函數dvmHeapBeginMarkStep用來初始化堆的標記范圍和Mark Stack,它的實現如下所示:
~~~
bool dvmHeapBeginMarkStep(bool isPartial)
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
if (!createMarkStack(&ctx->stack)) {
return false;
}
ctx->finger = NULL;
ctx->immuneLimit = (char*)dvmHeapSourceGetImmuneLimit(isPartial);
return true;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
在標記過程中用到的各種信息都保存一個GcMarkContext結構體描述的GC標記上下文ctx中。其中,ctx->stack描述的是Mark Stack,ctx->finger描述的是一個標記指紋,在遞歸標記對象時會用到,ctx->immuneLimit用來限定堆的標記范圍。
函數dvmHeapBeginMarkStep調用另外一個函數createMarkStack來初始化Mark Stack,它的實現如下所示:
~~~
static bool createMarkStack(GcMarkStack *stack)
{
assert(stack != NULL);
size_t length = dvmHeapSourceGetIdealFootprint() * sizeof(Object*) /
(sizeof(Object) + HEAP_SOURCE_CHUNK_OVERHEAD);
madvise(stack->base, length, MADV_NORMAL);
stack->top = stack->base;
return true;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數createMarkStack根據最壞情況來設置Mark Stack的長度,也就是用當前堆的大小除以對象占用的最小內存得到的結果。當前堆的大小可以通過函數dvmHeapSourceGetIdealFootprint來獲得。在堆上分配的對象,都是從Object類繼承下來的,因此,每一個對象占用的最小內存是sizeof(Object)。由于在為對象分配內存時,還需要額外的內存來保存管理信息,例如實際分配給對象的內存字節數。這些額外的管理信息占用的內存字節數通過宏HEAP_SOURCE_CHUNK_OVERHEAD來描述。
由于Mark Stack實際上是一個Object指針數組,因此有了上面的信息之后,我們就可以計算出Mark Stack的長度length。Mark Stack使用的內存塊在Dalvik虛擬機啟動的時候就創建好的,具體參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文,起始地址位于stack->base中。由于這塊內存開始的時候被函數madvice設置為MADV_DONTNEED,因此這里要將它重新設置為MADV_NORMAL,以便可以通知內核,這塊內存要開始使用了。
Mark Stack的棧頂stack->top指向的是Mark Stack內存塊的起始位置,以后就會從這個位置開始由小到大增長。
回到函數dvmHeapBeginMarkStep中,ctx->immuneLimit記錄的實際上是堆的起始標記位置。在此位置之前的對象,都不會被GC。這個位置是通過函數dvmHeapSourceGetImmuneLimit來確定的,它的實現如下所示:
~~~
void *dvmHeapSourceGetImmuneLimit(bool isPartial)
{
if (isPartial) {
return hs2heap(gHs)->base;
} else {
return NULL;
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
當參數isPartial等于true時,函數dvmHeapSourceGetImmuneLimit返回的是Active堆的起始位置,否則的話就返回NULL值。也就是說,如果當前執行的GC只要求回收部分垃圾,那么就只回收Active堆的垃圾,否則的話,就同時回收Active堆和Zygote堆的垃圾。
3. dvmHeapMarkRootSet
函數dvmHeapMarkRootSet用來的標記根集對象,它的實現如下所示:
~~~
/* Mark the set of root objects.
*
* Things we need to scan:
* - System classes defined by root classloader
* - For each thread:
* - Interpreted stack, from top to "curFrame"
* - Dalvik registers (args + local vars)
* - JNI local references
* - Automatic VM local references (TrackedAlloc)
* - Associated Thread/VMThread object
* - ThreadGroups (could track & start with these instead of working
* upward from Threads)
* - Exception currently being thrown, if present
* - JNI global references
* - Interned string table
* - Primitive classes
* - Special objects
* - gDvm.outOfMemoryObj
* - Objects in debugger object registry
*
* Don't need:
* - Native stack (for in-progress stuff in the VM)
* - The TrackedAlloc stuff watches all native VM references.
*/
void dvmHeapMarkRootSet()
{
GcHeap *gcHeap = gDvm.gcHeap;
dvmMarkImmuneObjects(gcHeap->markContext.immuneLimit);
dvmVisitRoots(rootMarkObjectVisitor, &gcHeap->markContext);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
通過注釋我們可以看到根集對象都包含了哪些對象。總的來說,就是包含兩大類對象。一類是Dalvik虛擬機內部使用的全局對象,另一類是應用程序正在使用的對象。前者會維護在內部的一些數據結構中,例如Hash表,后者維護在調用棧中。對這些根集對象進行標記都是通過函數dvmVisitRoots和回調函數rootMarkObjectVisitor進行的。
此外,我們還需要將不在堆回收范圍內的對象也看作是根集對象,以便后面可以使用統一的方法來遍歷這兩類對象所引用的其它對象。標記不在堆回收范圍內的對象是通過函數dvmMarkImmuneObjects來實現的,如下所示:
~~~
void dvmMarkImmuneObjects(const char *immuneLimit)
{
......
for (size_t i = 1; i < gHs->numHeaps; ++i) {
if (gHs->heaps[i].base < immuneLimit) {
assert(gHs->heaps[i].limit <= immuneLimit);
/* Compute the number of words to copy in the bitmap. */
size_t index = HB_OFFSET_TO_INDEX(
(uintptr_t)gHs->heaps[i].base - gHs->liveBits.base);
/* Compute the starting offset in the live and mark bits. */
char *src = (char *)(gHs->liveBits.bits + index);
char *dst = (char *)(gHs->markBits.bits + index);
/* Compute the number of bytes of the live bitmap to copy. */
size_t length = HB_OFFSET_TO_BYTE_INDEX(
gHs->heaps[i].limit - gHs->heaps[i].base);
/* Do the copy. */
memcpy(dst, src, length);
/* Make sure max points to the address of the highest set bit. */
if (gHs->markBits.max < (uintptr_t)gHs->heaps[i].limit) {
gHs->markBits.max = (uintptr_t)gHs->heaps[i].limit;
}
}
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
從前面分析的函數dvmHeapSourceGetImmuneLimit可以知道,參數immuneList要么是等于Zygote堆的最大地址值,要么是等于NULL。這取決于當前GC要執行的是全部垃圾回收還是部分垃圾回收。
函數dvmMarkImmuneObjects是在當前GC執行之前調用的,這意味著當前存活的對象都標記在Live Bitmap中。現在函數dvmMarkImmuneObjects要做的就是將不在回收范圍內的對象的標記位從Live Bitmap拷貝到Mark Bitmap去。具體做法就是分別遍歷Active堆和Zygote堆,如果它們處于不回范圍中,那么就對里面的對象在Live Bitmap中對應的內存塊拷貝到Mark Bitmap的對應位置去。
計算一個對象在一個Bitmap的標記位所在的內存塊是通過宏HB_OFFSET_TO_INDEX和HB_OFFSET_TO_BYTE_INDEX來實現的。這兩個宏的具體實現可以參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文。
回到函數dvmHeapMarkRootSet中,我們繼續分析函數dvmVisitRoots的實現,如下所示:
~~~
void dvmVisitRoots(RootVisitor *visitor, void *arg)
{
assert(visitor != NULL);
visitHashTable(visitor, gDvm.loadedClasses, ROOT_STICKY_CLASS, arg);
visitPrimitiveTypes(visitor, arg);
if (gDvm.dbgRegistry != NULL) {
visitHashTable(visitor, gDvm.dbgRegistry, ROOT_DEBUGGER, arg);
}
if (gDvm.literalStrings != NULL) {
visitHashTable(visitor, gDvm.literalStrings, ROOT_INTERNED_STRING, arg);
}
dvmLockMutex(&gDvm.jniGlobalRefLock);
visitIndirectRefTable(visitor, &gDvm.jniGlobalRefTable, 0, ROOT_JNI_GLOBAL, arg);
dvmUnlockMutex(&gDvm.jniGlobalRefLock);
dvmLockMutex(&gDvm.jniPinRefLock);
visitReferenceTable(visitor, &gDvm.jniPinRefTable, 0, ROOT_VM_INTERNAL, arg);
dvmUnlockMutex(&gDvm.jniPinRefLock);
visitThreads(visitor, arg);
(*visitor)(&gDvm.outOfMemoryObj, 0, ROOT_VM_INTERNAL, arg);
(*visitor)(&gDvm.internalErrorObj, 0, ROOT_VM_INTERNAL, arg);
(*visitor)(&gDvm.noClassDefFoundErrorObj, 0, ROOT_VM_INTERNAL, arg);
}
~~~
這個函數定義在文件dalvik/vm/alloc/Visit.cpp。
參數visitor指向的是函數rootMarkObjectVisitor,它負責將根集對象在Mark Bitmap中的位設置為1。我們后面再分析這個函數的實現。
以下對象將被視為根集對象:
1. 被加載到Dalvik虛擬機的類對象。這些類對象緩存在gDvm.loadedClasses指向的一個Hash表中,通過函數visitHashTable來遍歷和標記。
2. 在Dalvik虛擬機內部創建的原子類,例如Double、Boolean類等,通過函數visitPrimitiveTypes來遍歷和標記。
3. 注冊在調試器的對象。這些對象保存在gDvm.dbgRegistry指向的一個Hash表中,通過函數visitHashTable來遍歷和標記。
4. 字符串常量池中的String對象。這些對象保存保存在gDvm.literalStrings指向的一個Hash表中,通過函數visitHashTable來遍歷和標記。
5. 在JNI中創建的全局引用對象所引用的對象。這些被引用對象保存在gDvm.jniGlobalRefTable指向的一個間接引用表中,通過函數visitIndirectRefTable來遍歷和標記。
6. 在JNI中通過GetStringUTFChars、GetByteArrayElements等接口訪問字符串或者數組時被Pin住的對象。這些對象保存在gDvm.jniPinRefTable指向的一個引用表中,通過函數visitReferenceTable來遍歷和標記。
7. 當前位于Davlik虛擬機線程的調用棧的對象。這些對象記錄在棧幀中,通過函數visitThreads來遍歷和標記。
8. 在Dalvik虛擬機內部創建的OutOfMemory異常對象,這個對象保存在gDvm.outOfMemoryObj中。
9. 在Dalvik虛擬機內部創建的InternalError異常對象,這個對象保存在gDvm.internalErrorObj中。
10. 在Dalvik虛擬機內部創建的N oClassDefFoundError異常對象,這個對象保存在gDvm.noClassDefFoundErrorObj中。
在上述這些根集對象中,最復雜和最難遍歷和標記的就是位于Dalvik虛擬機線程棧中的對象,因此接下來我們就繼續分析函數發isitThreads的實現,如下所示:
~~~
static void visitThreads(RootVisitor *visitor, void *arg)
{
Thread *thread;
assert(visitor != NULL);
dvmLockThreadList(dvmThreadSelf());
thread = gDvm.threadList;
while (thread) {
visitThread(visitor, thread, arg);
thread = thread->next;
}
dvmUnlockThreadList();
}
~~~
這個函數定義在文件dalvik/vm/alloc/Visit.cpp。
所有的Dalvik虛擬機線程都保存在gDvm.threadList指向的一個列表中,函數visitThreads通過調用另外一個函數函數visitThread來遍歷和標記每一個Dalvik虛擬機棧上對象。后者的實現如下所示:
~~~
static void visitThread(RootVisitor *visitor, Thread *thread, void *arg)
{
u4 threadId;
assert(visitor != NULL);
assert(thread != NULL);
threadId = thread->threadId;
(*visitor)(&thread->threadObj, threadId, ROOT_THREAD_OBJECT, arg);
(*visitor)(&thread->exception, threadId, ROOT_NATIVE_STACK, arg);
visitReferenceTable(visitor, &thread->internalLocalRefTable, threadId, ROOT_NATIVE_STACK, arg);
visitIndirectRefTable(visitor, &thread->jniLocalRefTable, threadId, ROOT_JNI_LOCAL, arg);
if (thread->jniMonitorRefTable.table != NULL) {
visitReferenceTable(visitor, &thread->jniMonitorRefTable, threadId, ROOT_JNI_MONITOR, arg);
}
visitThreadStack(visitor, thread, arg);
}
~~~
這個函數定義在文件dalvik/vm/alloc/Visit.cpp。
對于每一個Dalvik虛擬機線程來說,除了它當前棧上的對象需要標記為根集對象之外,還有以下對象需要標記為根集對象:
1. 用來描述Dalvik虛擬機線程的線程對象。這個對象保存在thread->threadObj中。
2. 當前Davik虛擬機線程的未處理異常對象。這個對象保在在thread->expception中。
3. Dalvik虛擬機線程內部創建的對象,例如執行過程中拋出的異常對象。這些對象保存在thread->internalLocalRefTable指向的一個引用表中。通過函數visitReferenceTable來遍歷和標記。
4. Dalvik虛擬機線程在JNI中創建的局部引用對象。這些對象保存在thread->jniLocalRefTable指向的一個間接引用表中,通過函數visitIndirectRefTable來遍歷和標記。
5. Dalvik虛擬機線程在JNI中創建的用于同步的Monitor對象。這些對象保存在thread->jniMonitorRefTable指向的一個引用表中,通過函數visitReferenceTable來遍歷和標記。
當前棧上的對象通過函數visitThreadStack來遍歷和標記,它的實現如下所示:
~~~
static void visitThreadStack(RootVisitor *visitor, Thread *thread, void *arg)
{
......
u4 threadId = thread->threadId;
const StackSaveArea *saveArea;
for (u4 *fp = (u4 *)thread->interpSave.curFrame;
fp != NULL;
fp = (u4 *)saveArea->prevFrame) {
Method *method;
saveArea = SAVEAREA_FROM_FP(fp);
method = (Method *)saveArea->method;
if (method != NULL && !dvmIsNativeMethod(method)) {
const RegisterMap* pMap = dvmGetExpandedRegisterMap(method);
const u1* regVector = NULL;
if (pMap != NULL) {
......
int addr = saveArea->xtra.currentPc - method->insns;
regVector = dvmRegisterMapGetLine(pMap, addr);
}
if (regVector == NULL) {
......
for (size_t i = 0; i < method->registersSize; ++i) {
if (dvmIsValidObject((Object *)fp[i])) {
(*visitor)(&fp[i], threadId, ROOT_JAVA_FRAME, arg);
}
}
} else {
......
u2 bits = 1 << 1;
for (size_t i = 0; i < method->registersSize; ++i) {
bits >>= 1;
if (bits == 1) {
.......
bits = *regVector++ | 0x0100;
}
if ((bits & 0x1) != 0) {
......
(*visitor)(&fp[i], threadId, ROOT_JAVA_FRAME, arg);
}
}
dvmReleaseRegisterMapLine(pMap, regVector);
}
}
......
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/Visit.cpp。
在Dalvik虛擬機中,每一個Dalvik虛擬機線程都用一個Thread結構體來描述。在Thread結構體中,有一個成員變量interpSave,它指向的是一個InterpSaveState結構體。在InterpSaveState結構體中,又有一個成員變量curFrame,它指向線程的當前調用棧幀,如圖2所示:

圖2 Dalvik虛擬機線程的調用棧
在圖2中,Dalvik虛擬機調用棧由高地址往低地址增長,即棧頂位于低地址。在每一個調用幀前面,有一個類型為StackSaveArea的結構體。在StackSaveArea結構體中,有四個重要的成員變量prevFrame、savedPc、method和currentPc。其中,prevFrame指向前一個調用幀,savedPc指向當前函數調用完成后的返回地址,method指向當前調用的函數,currentPc指向當前執行的指令。通過成員變量prevFrame,就可以從當前調用棧幀開始,遍歷整個調用棧。此外,通過成員變量method,可以知道一個調用棧幀對應的調用函數是一個Java函數還是一個JNI函數。如果是一個JNI函數,那么是不需要檢查它的棧幀的。因為JNI函數不會在里面保存Java對象。
那么在JNI函數里面創建或者訪問的對象是如何管理的呢?我們在JNI函數中需要創建或者訪問Java對象時,都必須要通過JNI接口來進行。JNI接口會把JNI函數創建或者訪問的Java對象保存線程相關的一個JNI Local Reference Table中。這些被JNI Local Reference Table引用的Java對象,在JNI函數調用結束的時候,會相應地被自動釋放引用。但是有些情況下,我們需要在JNI函數中手動釋放被JNI Local Reference Table引用的Java對象,因為JNI Local Reference Table的大小是有限的。一旦一個JNI函數引用的Java對象數大于JNI Local Reference Table的容量,那么就會發生錯誤。這種情況特別容易發生循環語句中。
由于JNI函數里面創建或者訪問的對象保存在JNI Local Reference Table中,因此,在前面分析的函數visitThread中,需要對每一個Dalvik虛擬機線程的JNI Local Reference Table中的Java對象進行遍歷和標記,避免它們在GC過程中被回收。
好了,我們現在將目光返回到Dalvik虛擬機線程的調用棧中,我們需要做的是遍歷那些非JNI函數調用,并且對位于棧幀里面的Java對象進行標記,以免它們在GC過程中被回收。我們知道,Dalvik虛擬機指令是基于寄存器的。也就是說,無論函數里面的參數,還是局部變量,都是保存在寄存器中。但是,我們需要注意的是。這些寄存器不是真實的硬件寄存器,而是虛擬出來的。那么是怎么虛擬出來的呢?其實就是將寄存器映射到調用棧幀對就的內存塊上。因此,我們只需要遍歷調用棧幀對應的內存塊,就可以知道線程的調用棧都引用了哪些Java對象,從而可以對它們進行標記。
現在,新的問題又出現了。調用棧幀的數據不一定都是Java對象引用,還有可能是一個原子類型,例如整數、布爾變量或者浮點數。在遍歷調用棧幀的時候,我們是怎么知道里面的一個數據到底是Java對象引用還是一個原子類型呢?我們可以想到的一個辦法是判斷它們值。如果它們的值是位于Java堆的范圍內,那么就認為它們是Java對象引用。但是這樣做是不準確的,因為有可能這是一個整數值,并且這個整數值剛好落在Java堆的地址范圍之內。不過,我們寧愿將一個整數值誤認為一個Java對象引用,也比漏掉一個Java對象引用好。因為將一個整數值誤認為一個Java對象引用,導致的后果僅僅是垃圾不能及時回收,而漏掉一個Java對象引用,則意味著一個正在使用Java對象還在被引用的時候被回收。
上面描述的在調用棧幀中,大小值只要是落在Java堆的地址范圍之內就認為是Java對象引用的做法稱為保守GC算法,與此相對的稱為準確GC。在準確GC中,用一個稱為Register Map的數據結構來輔助GC。Register Map記錄了一個Java函數所有可能的GC執行點所對應的寄存器使用情況。如果在某一個GC執行點,某一個寄存器對應的是一個Java對象引用,那么在對應的Register Map中,就有一位被標記為1。
從前面我們分析的函數dvmSuspendAllThreads可以知道,每當一個Dalvik虛擬機線程被掛起等待GC時,它們總是掛起在IF、GOTO、SWITCH、RETURN和THROW等跳轉指令中。這些指令點對應的實際上就GC執行點。因此,我們可以在一個函數中針對出現上述指令的地方,均記錄函數當前的寄存器使用情況,從而可以實現準確GC。那么,這個工作是由誰去做的呢?我們知道,APK在安裝的時候,安裝服務會它們的DEX字節碼進行優化,得到一個odex文件。實際上,APK在安裝的時候,它們的DEX字節碼除了會被優化之外,還會被驗證和分析。驗證是為了保證不包含非法指令,而分析就是為了得到指令的執行狀況,其中就包括得到每一個GC執行點的寄存器使用情況,最終形成一個Register Map,并且保存在odex文件中。這樣,當一個odex文件被加載使用的時候,我們就可以直接獲得一個函數在某一個GC執行點的寄存器使用情況。
函數visitThreadStack在遍歷調用棧幀的過程中,通過調用函數dvmGetExpandedRegisterMap可以獲得當前調用函數對應的Register Map。有了這個Register Map之后,再通過在當前StackSaveArea結構體的成員變量currentPc的值,就可以獲得一個用來描述調用函數當前寄存器使用情況的一個寄存器向量regVector。在獲得的寄存器向量regVector中,如果某一個位的值等于1,那么就說明對應的寄存器保存的是一個Java對象引用。在這種情況下,就需要將該Java對象標記為活的。
注意,函數visitThreadStack按照字節來訪問寄存器向量regVector。每次讀出1個字節的數據到變量bits的0~7位,然后再將變量bits的第8位設置為1。此后從低位開始,每訪問一位就將變量bits向右移一位。當向左移夠8位后,就能保證變量bits的值變為1,這時候就再從寄存器向量regVector讀取下一字節進行遍歷。
由于Register Map不是強制的,因此有可能某些函數不存在對應的Register Map,在這種情況下,就需要使用前面我們所分析的保守GC算法,遍歷調用棧幀的所有數據,只要它們的值在Java堆的范圍之內,均認為它們是Java對象引用,并且對它們進行標記。
在前面分析的一系列函數,都是通過調用函數rootMarkObjectVisitor來對對象進行標記的,它的實現如下所示:
~~~
static void rootMarkObjectVisitor(void *addr, u4 thread, RootType type,
void *arg)
{
......
Object *obj = *(Object **)addr;
GcMarkContext *ctx = (GcMarkContext *)arg;
if (obj != NULL) {
markObjectNonNull(obj, ctx, false);
}
}
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
參數addr指向的是Java對象地址,arg指向一個描述當前GC標記上下文的GcMarkContext結構體。函數rootMarkObjectVisitor通過調用另外一個函數markObjectNonNull來標記參數addr所描述的Java對象。
函數markObjectNonNull的實現如下所示:
[cpp] view plain copy
static void markObjectNonNull(const Object *obj, GcMarkContext *ctx,
bool checkFinger)
{
......
if (obj < (Object *)ctx->immuneLimit) {
assert(isMarked(obj, ctx));
return;
}
if (!setAndReturnMarkBit(ctx, obj)) {
/* This object was not previously marked.
*/
if (checkFinger && (void *)obj < ctx->finger) {
/* This object will need to go on the mark stack.
*/
markStackPush(&ctx->stack, obj);
}
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數markObjectNonNull不僅在標記根集對象的時候會調用到,在遞歸標記被根集對象所引用的對象的時候也會調用到。在標記根集對象調用時,參數checkFlinger的值等于false,而在遞歸標記被根集對象所引用的對象時,參數checkFlinger的值等于true,并且參數ctx指向的GcMarkContext結構體的成員變量finger被設置為當前遍歷過的Java對象的最大地址值。
前面提到,對于在非回收范圍內的Java對象,它們一開始的時候就已經標記為存活的了,因此,函數markObjectNonNull一開始就判斷一個參數obj描述的Java對象是否在非回收堆范圍內。如果是的話,那么就不需要重復對其它進行標記了。
對于在回收范圍內的Java對象,則需要調用函數setAndReturnMarkBit將其在Mark Bitmap中對應的位設置為1。函數setAndReturnMarkBit在將一個位設置為1之前,要返回該位的舊值。也就是說,當函數setAndReturnMarkBit的返回值等于0時,就表示一個Java對象是第一次被標記。在這種情況下,如果參數checkFlinger的值等于true,并且被遍歷的Java對象的地址值小于參數ctx指向的GcMarkContext結構體的成員變量finger,那么就需要調用函數markStackPush將該對象壓入Mark Stack中,以便后面可以繼續對該對象進行遞歸遍歷。
函數setAndReturnMarkBit的實現如下所示:
~~~
static long setAndReturnMarkBit(GcMarkContext *ctx, const void *obj)
{
return dvmHeapBitmapSetAndReturnObjectBit(ctx->bitmap, obj);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
從前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文可以知道,ctx->bitmap指向的是Mark Bitmap,因此函數setAndReturnMarkBit是將參數obj描述的Java對象在Mark Bitmap中對應的位設置為1。
函數markStackPush的實現如下所示:
~~~
static void markStackPush(GcMarkStack *stack, const Object *obj)
{
......
*stack->top = obj;
++stack->top;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
從這里就可以看出,函數markStackPush將參數obj描述的Java對象壓入到Mark Stack中。凡是在Mark Stack中的Java對象,都是需要遞歸遍歷它們所引用的Java對象的。
這樣,當函數dvmHeapMarkRootSet調用完畢,所有的根集對象在Mark Bitmap中對應的位就都被設置為1了。
4. dvmClearCardTable
函數dvmClearCardTable用來清零Card Table,它的實現如下所示:
~~~
void dvmClearCardTable()
{
......
if (gDvm.lowMemoryMode) {
// zero out cards with madvise(), discarding all pages in the card table
madvise(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength, MADV_DONTNEED);
} else {
// zero out cards with memset(), using liveBits as an estimate
const HeapBitmap* liveBits = dvmHeapSourceGetLiveBits();
size_t maxLiveCard = (liveBits->max - liveBits->base) / GC_CARD_SIZE;
maxLiveCard = ALIGN_UP_TO_PAGE_SIZE(maxLiveCard);
if (maxLiveCard > gDvm.gcHeap->cardTableLength) {
maxLiveCard = gDvm.gcHeap->cardTableLength;
}
memset(gDvm.gcHeap->cardTableBase, GC_CARD_CLEAN, maxLiveCard);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/CardTable.cpp中。
在低內存模式下,函數dvmClearCardTable調用另外一個函數madvice將Card Table對應的虛擬內存塊標記為MADV_DONTNEED,以便這塊虛擬內存對應的物理內存頁可以被內核回收。但是當Card Table對應的虛擬內存塊被訪問的時候,內核會重新給它映射物理頁,并且會將被映射的物理頁的值初始化為0。通過這種巧妙的方式,不僅可以將Card Table清零,而且還可以釋放暫時不使用的內存,符合低內存模式運行的要求。不過我們也應該注意到,這種做法實際上是通過犧牲程序性能來換取空間的,因為重新給一塊虛擬內存映射物理頁,以及對該物理頁進行初始化都是需要花費時間的。在計算機的世界時,根據實際需要,以時間換空間,或者以空間換時間,都是經典的做法。
在非低內存模式下,函數dvmClearCardTable并沒有對整個Card Table進行清零。從前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文可以知道,Card Table雖然只有Heap Bitmap一半的大小,但是當堆較大時,Card Table的絕對值仍然是不小的。而且每次執行GC時,整個堆并沒有完全用完。因此,函數dvmClearCardTable就根據當前存活的地址值最大的Java對象來評估應該清零的Card Table大小。一來是因為只有當前存活的Java對象才可能在并行GC期間訪問其引用的其它Java對象,二來是不清零部分不會對程序產生影響。試想未清零部分的Card Table,如果它的值本來就于等于0,那么就已經是我們想要的結果了。另一方面,如果未清零部分的Card Table的值等于1,那么產生的后果僅僅就是造成一些垃圾對象沒有及時回收而已,但是不會影響程序的正常邏輯。計算出應該清零的部分Card Table大小之后,就調用函數memset來將它們的值設置為GC_CARD_CLEAN,即設置為0。
5. dvmResumeAllThreads
函數dvmResumeAllThreads用來喚醒之前被掛起的線程,它的實現如下所示:
~~~
void dvmResumeAllThreads(SuspendCause why)
{
Thread* self = dvmThreadSelf();
Thread* thread;
lockThreadSuspend("res-all", why); /* one suspend/resume at a time */
......
dvmLockThreadList(self);
lockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
{
continue;
}
if (thread->suspendCount > 0) {
dvmAddToSuspendCounts(thread, -1,
(why == SUSPEND_FOR_DEBUG ||
why == SUSPEND_FOR_DEBUG_EVENT)
? -1 : 0);
} else {
LOG_THREAD("threadid=%d: suspendCount already zero",
thread->threadId);
}
}
unlockThreadSuspendCount();
dvmUnlockThreadList();
......
unlockThreadSuspend();
......
lockThreadSuspendCount();
int cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond);
......
unlockThreadSuspendCount();
......
}
~~~
這個函數定義在文件alvik/vm/Thread.cpp中。
對照我們前面分析的函數dvmSuspendAllThreads的實現,就可以比較容易理解函數dvmResumeAllThreads的實現了。在GC期間,GC線程需要掛起其它的Dalvik虛擬機線程時,就將它們的Suspend Count增加1。各個Dalvik虛擬機線程在執行的過程中,會周期性地檢查自己的Suspend Count。一旦發現自己的Suspend Count大于0,那么就會將自己掛起,等待GC線程喚醒。
我們以GOTO指令的執行過程說明一個Dalvik虛擬機自愿掛起自己的過程。通過前面[Dalvik虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)一文的學習,我們很容易找到GOTO指令的解釋執行過程,如下所示:
~~~
HANDLE_OPCODE(OP_GOTO /*+AA*/)
vdst = INST_AA(inst);
if ((s1)vdst < 0)
ILOGV("|goto -0x%02x", -((s1)vdst));
else
ILOGV("|goto +0x%02x", ((s1)vdst));
ILOGV("> branch taken");
if ((s1)vdst < 0)
PERIODIC_CHECKS((s1)vdst);
FINISH((s1)vdst);
OP_END
~~~
這段代碼定義在文件dalvik/vm/mterp/out/InterpC-portable.cpp中。
如果是向后跳轉,那么就會調用宏PERIODIC_CHECKS來檢查是否需要掛起當前線程。
宏PERIODIC_CHECKS的定義如下所示:
~~~
#define PERIODIC_CHECKS(_pcadj) { \
if (dvmCheckSuspendQuick(self)) { \
EXPORT_PC(); /* need for precise GC */ \
dvmCheckSuspendPending(self); \
} \
}
~~~
這個宏定義在文件dalvik/vm/mterp/out/InterpC-portable.cpp中。
宏PERIODIC_CHECKS首先是調用函數dvmCheckSuspendQuick來檢查保存在當前線程對象里面的一個kSubModeSuspendPending標志位是否等于1。當這個kSubModeSuspendPending等于1時候,就說明線程的Suspend Count值大于0。換句話說,每當一個線程的Suspend Count增加1之后的值大于0,那么都會相應地將對應的線程對對象的kSubModeSuspendPending標志位設置為1。以便后面可以用來快速檢測線程是否有掛起請求。
在當前線程的Suspend Count大于0的情況下,宏PERIODIC_CHECKS接下來做兩件事情。第一件事情是調用宏EXPORT_PC將當前的PC值記錄在當前調用棧幀的StackSaveArea結構體中的成員變量currentPc中,以便可以執行準確GC。這一點是我們前面分析過的。第二件事情就是調用函數dvmCheckSuspendPending來將自己掛起。
函數dvmCheckSuspendPending的實現如下所示:
~~~
bool dvmCheckSuspendPending(Thread* self)
{
assert(self != NULL);
if (self->suspendCount == 0) {
return false;
} else {
return fullSuspendCheck(self);
}
}
~~~
這個函數定義在文件dalvik/vm/Thread.cpp中。
在我們這個情景中,當前線程的Suspend Count肯定是不等于0的,因此,接下來就會調用函數fullSuspendCheck。
函數fullSuspendCheck的實現如下所示:
~~~
static bool fullSuspendCheck(Thread* self)
{
......
lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */
bool needSuspend = (self->suspendCount != 0);
if (needSuspend) {
......
ThreadStatus oldStatus = self->status; /* should be RUNNING */
self->status = THREAD_SUSPENDED;
......
while (self->suspendCount != 0) {
......
dvmWaitCond(&gDvm.threadSuspendCountCond,
&gDvm.threadSuspendCountLock);
}
......
self->status = oldStatus;
......
}
unlockThreadSuspendCount();
return needSuspend;
}
~~~
這個函數定義在文件dalvik/vm/Thread.cpp中。
從這里就可以清楚地看到,函數fullSuspendCheck在當前線程的Supend Count不等于0的情況下,首先是將自己的狀態設置為THREAD_SUSPENDED,接著掛起在條件變量gDvm.threadSuspendCountCond上,直到被喚醒為止,然后再恢復為之前的狀態。
有了這些背景知識之后,回到函數dvmResumeAllThreads中,我們就更容易理解它的實現了。它所要做的就是將每一個被掛起的Dalvik虛擬機線程的Suspend Count減少1,并且在最后將掛起在條件變量gDvm.threadSuspendCountCond上的所有線程都喚醒。
6. dvmHeapScanMarkedObjects
函數dvmHeapScanMarkedObjects用來遞歸標記根集對象所引用的其它對象,它的實現如下所示:
~~~
void dvmHeapScanMarkedObjects(void)
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
......
dvmHeapBitmapScanWalk(ctx->bitmap, scanBitmapCallback, ctx);
ctx->finger = (void *)ULONG_MAX;
......
processMarkStack(ctx);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
前面已經將根集對象標記在ctx->bitmap指向的Mark Bitmap了,現在就開始調用函數dvmHeapBitmapScanWalk來標記它們直接引用的對象。
函數dvmHeapBitmapScanWalk的實現如下所示:
~~~
void dvmHeapBitmapScanWalk(HeapBitmap *bitmap,
BitmapScanCallback *callback, void *arg)
{
......
uintptr_t end = HB_OFFSET_TO_INDEX(bitmap->max - bitmap->base);
uintptr_t i;
for (i = 0; i <= end; ++i) {
unsigned long word = bitmap->bits[i];
if (UNLIKELY(word != 0)) {
unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);
uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + bitmap->base;
void *finger = (void *)(HB_INDEX_TO_OFFSET(i + 1) + bitmap->base);
while (word != 0) {
const int shift = CLZ(word);
Object *obj = (Object *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);
(*callback)(obj, finger, arg);
word &= ~(highBit >> shift);
}
end = HB_OFFSET_TO_INDEX(bitmap->max - bitmap->base);
}
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數dvmHeapBitmapScanWalk遍歷Mark Bitmap中值等于1的位,并且調用參烽callback指向的函數來標記它們引用的對象。在遍歷的過程中,主要用到了HB_BITS_PER_WORD、HB_INDEX_TO_OFFSET、HB_OBJECT_ALIGNMENT和HB_OFFSET_TO_INDEX這四個宏,它們的具體實現可以參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文。
參數callback指向的函數指向的函數為scanBitmapCallback,它的實現如下所示:
~~~
static void scanBitmapCallback(Object *obj, void *finger, void *arg)
{
GcMarkContext *ctx = (GcMarkContext *)arg;
ctx->finger = (void *)finger;
scanObject(obj, ctx);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
參數obj指向要遍歷的對象,finger指向當前遍歷的Java對象的最大地址,arg指向一個GcMarkContext結構體。Java對象是按照地址值從小到大的順序遍歷的,因此這里傳進來的參數finger的值也是遞增的。
函數scanBitmapCallback將參數finger的值保存在ctx->finger之后,就調用函數scanObject來標記參數obj指向的對象所引用的對象,它的實現如下所示:
~~~
static void scanObject(const Object *obj, GcMarkContext *ctx)
{
......
if (obj->clazz == gDvm.classJavaLangClass) {
scanClassObject(obj, ctx);
} else if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {
scanArrayObject(obj, ctx);
} else {
scanDataObject(obj, ctx);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
不同類型的對象調用不同的函數來遍歷。其中,類對象使用函數scanClassObject來遍歷,數組對象使用函數scanArrayObject來遍歷,普通對象使用函數scanDataObject來遍歷。
接下來,我們主要分析普通對象的標記過程,即函數scanDataObject的實現,如下所示:
~~~
static void scanDataObject(const Object *obj, GcMarkContext *ctx)
{
......
markObject((const Object *)obj->clazz, ctx);
scanFields(obj, ctx);
if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISREFERENCE)) {
delayReferenceReferent((Object *)obj, ctx);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
注意,參數obj指向的對象是已經標記過了的,因此,函數scanDataObject只標記它所引用的對象,其中就包括它所引用的類對象obj->clazz,以及它的引用類型的成員變量所引用的對象。前者直接通過函數markObject來標記,后者通過函數scanField依次遍歷標記,不過最終也是通過調用函數markObject來標記每一個被引用對象。因此,接下來我就主要分析函數markObject的實現,如下所示:
~~~
static void markObject(const Object *obj, GcMarkContext *ctx)
{
if (obj != NULL) {
markObjectNonNull(obj, ctx, true);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
這里我們又看到前面已經分析過的函數markObjectNonNull,不同的是這里指定的第三個參數為true,表示要將那些地址值小于ctx->finger的并且是第一次被標記的Java對象壓入到Mark Stack去,以便后面可以繼續對它們進行遞歸遍歷。
回到函數scanDataObject中,對于引用類型的對象,需要調用函數delayReferenceReferent對它們所引用的目標對象進行特殊處理,如下所示:
~~~
static void delayReferenceReferent(Object *obj, GcMarkContext *ctx)
{
......
GcHeap *gcHeap = gDvm.gcHeap;
size_t pendingNextOffset = gDvm.offJavaLangRefReference_pendingNext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
Object *pending = dvmGetFieldObject(obj, pendingNextOffset);
Object *referent = dvmGetFieldObject(obj, referentOffset);
if (pending == NULL && referent != NULL && !isMarked(referent, ctx)) {
Object **list = NULL;
if (isSoftReference(obj)) {
list = &gcHeap->softReferences;
} else if (isWeakReference(obj)) {
list = &gcHeap->weakReferences;
} else if (isFinalizerReference(obj)) {
list = &gcHeap->finalizerReferences;
} else if (isPhantomReference(obj)) {
list = &gcHeap->phantomReferences;
}
......
enqueuePendingReference(obj, list);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
引用類型的對象,例如SoftReference、WeakReference、PhantomReference和FinalizerReference,都是從父類Reference繼承下來的。在父類Reference中,有兩個成員變量pendingNext和referent。前者用來將相同類型的引用對象鏈接起來形成一個鏈表,等待GC處理,只在虛擬機內部使用,后者指向目標對象。
如果一個引用對象還沒有加入到Dalvik虛擬機內部的鏈表中,即其成員變量pendingNext的值等于NULL,并且它引用了目標對象,即其成員變量referent的值不等于NULL,并且它還沒有被標記過,那么就要將它加入到相應的列表去等待GC處理。
不同類型的引用對象被加入到不同的列表等待處理,其中:
1. 軟引用對象加入到gcHeap->softReferences列表;
2. 弱引用對象加入到gcHeap->weakReferences列表;
3. finalizer引用對象,即重寫了成員函數finalize的對象,加入到gcHeap->finalizerReferences列表;
4. 影子引用對象加入到gcHeap->phantomReferences列表。
將一個引用對象加入到指定的列表是通過函數enqueuePendingReference來實現的,實際上就是通過引用對象的成員變量pendingNext鏈入到列表中。
執行完成上述的函數后,被根集對象引用的一部分對象就也被遞歸標記了。回到函數dvmHeapScanMarkedObjects中,接下來需要繼續調用函數processMarkStack來遞歸標記還沒有被標記的另外一部分對象。
在分析函數processMarkStack的實現之前,我們首先要明確哪些是該標記又未標記的對象。我們首先要明確兩點:
1. 在遍歷Mark Bitmap之前,根集對象已經被標記;
2. 遍歷Mark Bitmap是按地址值從小到大的順序遍歷的。
這意味著,遍歷完成Mark Bitmap之后,以下對象都是已經被標記的:
1. 根集對象;
2. 被根集對象直接或者間接引用并且地址值比當前遍歷的對象還要大的對象。
這時候該標記還沒有標記的對象就剩下那些被根集對象直接或者間接引用并且地址值比當前遍歷的對象還要小的對象。這些對象統統被壓入到了Mark Stack中,因此,函數processMarkStack只要繼續遞歸遍歷Mark Stack中的對象就可以對那些該標記還沒有被標記的對象進行標記。
舉個例子說,有四個對象A、B、C、D、E和F,其中,C和D是根集對象,它們的地址址依次遞增,C引用了B,D引用了E。B又引用了A和F。遍歷Mark Bitmap的時候,依次發生以下事情:
1. C被遍歷;
2. C引用了B,但是由于其地址值比B大,因此B被標記并且被壓入Mark Stack中;
3. D被遍歷;
4. D引用了E,但是由于其地址值比D大,因此E被標記;
5. 由于E被標記了,因此E也會被遍歷。
遍歷完成Mark Bitmap之后,Mark Stack被壓入了B,因此要從Mark Stack中彈出B,并且對B進行遍歷。遍歷B的過程如下所示:
1. B引用了A,因此A被標記同時也會被壓入到Mark Stack中;
2. B引用了F,因此F也會被標標記以及壓入到Mark Stack中;
現在Mark Stack又被壓入了A和F,因此又要繼續將它們彈出進行遍歷:
1. 遍歷A,它沒有引用其它對象,因此沒有對象被壓入Mark Stack中;
2. 遍歷F。它也沒有引用其它對象,因此也沒有對象被村入Mark Stack中。
此輪遍歷結束后,Mark Stack為空,因此所有被根集對象直接或者間接引用均被標記。
有了上述背景知識之后,函數processMarkStack的實現就一目了然了,它的實現如下所示:
~~~
static void processMarkStack(GcMarkContext *ctx)
{
assert(ctx != NULL);
assert(ctx->finger == (void *)ULONG_MAX);
assert(ctx->stack.top >= ctx->stack.base);
GcMarkStack *stack = &ctx->stack;
while (stack->top > stack->base) {
const Object *obj = markStackPop(stack);
scanObject(obj, ctx);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
在調用函數processMarkStack之前,ctx->finger的值已經被設置為ULONG_MAX,因此,在接下來的對象標記過程中,只要被引用對象是第一次被標記,都會被壓入到Mark Stack中進行遞歸遍歷,直到Mark Stack變成空為止。
函數processMarkStack是通過調用函數scanObject來標記被Mark Stack中的對象所引用的對象的。這個函數前面已經分析過了,這里就不再復述。
這樣,當函數dvmHeapScanMarkedObjects調用完畢,所有的被根集對象直接或者間接引用的對象都被遞歸地在Mark Bitmap中標記了。
7. dvmHeapReMarkRootSet
函數dvmHeapReMarkRootSet用來重新標記根集對象,在并行GC中調用,它的實現如下所示:
~~~
void dvmHeapReMarkRootSet()
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
assert(ctx->finger == (void *)ULONG_MAX);
dvmVisitRoots(rootReMarkObjectVisitor, ctx);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
與前面分析的函數dvmHeapMarkRootSet類似,函數dvmHeapReMarkRootSet也是調用函數dvmVisitRoots來重新遍歷根集對象,不過使用的是另外一個函數rootReMarkObjectVisitor來標記根集對象。
函數rootReMarkObjectVisitor的實現如下所示:
~~~
static void rootReMarkObjectVisitor(void *addr, u4 thread, RootType type,
void *arg)
{
......
Object *obj = *(Object **)addr;
GcMarkContext *ctx = (GcMarkContext *)arg;
if (obj != NULL) {
markObjectNonNull(obj, ctx, true);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
與前面分析的函數rootMarkObjectVisitor類似,函數rootReMarkObjectVisitor也是使用函數markObjectNonNull來標記根集對象,不同的是后者在調用函數markObjectNonNull時,第三個參數指定為true,并且在此之前,ctx->finger的值已經被設置為ULONG_MAX,因此,被根集對象直接引用的對象都會被壓入到Mark Stack中。
這些被壓入到Mark Stack中的對象后面會進一步得到處理。
8. dvmHeapReScanMarkedObjects
函數dvmHeapReScanMarkedObjects用來標記那些在并行GC執行過程被引用的對象,它的實現如下所示:
~~~
void dvmHeapReScanMarkedObjects()
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
/*
* The finger must have been set to the maximum value to ensure
* that gray objects will be pushed onto the mark stack.
*/
assert(ctx->finger == (void *)ULONG_MAX);
scanGrayObjects(ctx);
processMarkStack(ctx);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數dvmHeapReScanMarkedObjects首先是調用函數scanGrayObjects標記那些在并行GC過程中被修改的對象,這些對象稱為灰色(Gray)對象。更通俗地說,灰色對象就是那些自己已經被標記但是自己引用的對象還沒有被標記的對象,它們以后需要再次遍歷和標記引用的對象。相應地,也存在黑色(Black)對象和白色(White)對象。黑色對象是指那些自己已經被標記并且自己引用的對象也已經被標記的對象,而白色對象就是那些沒有被標記的對象,它們是要被回收的。
在前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文中,我們提到,Card Table主要是用來在并行執行的、并且只進行部分垃圾回收的GC。這一點從這里就可以得到體現。首先,在并行GC的標記階段,GC線程和其它線程是同時運行的,這樣其它線程就有機會去操作對象,這樣會造成GC線程標錯對象或者漏標對象。標錯對象不會對程序的正確性造成影響,頂多就是造成標錯對象不能及時回收。但是漏標對象問題就大了,這會造成還被引用的對象被GC回收掉。因此,并行GC就需要執行兩遍標記階段。第一遍是GC線程和其它線程同時運行的,而第二遍是只有GC線程運行的。由于大部分對象在第一遍標記階段已經被標記,因此第二遍真正需要標記的對象不會太多,這樣就可以保證第二遍標記階段可以快速完成。
現在我們分兩種情況進行考慮。第一種情況是當前執行的是全部垃圾收集,即在Zygote堆和Active堆上分配對象都進行回收。這時候實際上我們通過函數processMarkStack遞歸標記函數dvmHeapReMarkRootSet重新標記的根集對象所引用的其它對象,就可以保證不會漏標對象。第二種情況下是當前執行的部分垃圾收集,即只回收在Active堆上分配的對象。這時候如果在GC線程和其它線程運行階段,其它線程修改了在Zygote堆上分配的對象,造成在Zygote堆上分配的對象引用了一個在Active堆上分配的、原來沒有被標記的、并且也不在新的根集內的對象,那么就一定會造成這個在Active堆分配的對象被錯誤地回收。不過這時好在Card Table記錄了這個在Zygote堆上分配的、被修改了的對象,因此就可以調用函數scanGaryObjects來遞歸標記它所引用的在Active堆上分配的、原來沒有被標記的、并且也不在新的根集內的對象,保證這些被重新引用的對象不會被錯誤回收。
注意,函數scanGrayObjects來遍歷和標記灰色對象的過程中,也會將那些被灰色對象引用的其它對象壓入到Mark Stack中。這樣在Mark Stack中,就匯集了之前由新的根集對象和灰色對象所引用的其它對象,再通過調用前面分析的函數processMarkStack,就可以遞歸地將所有這些被引用的對象進行標記。
接下來,我們就重點分析函數scanGrayObjects的實現,如下所示:
~~~
static void scanGrayObjects(GcMarkContext *ctx)
{
GcHeap *h = gDvm.gcHeap;
const u1 *base, *limit, *ptr, *dirty;
base = &h->cardTableBase[0];
// The limit is the card one after the last accessible card.
limit = dvmCardFromAddr((u1 *)dvmHeapSourceGetLimit() - GC_CARD_SIZE) + 1;
assert(limit <= &base[h->cardTableOffset + h->cardTableLength]);
ptr = base;
for (;;) {
dirty = (const u1 *)memchr(ptr, GC_CARD_DIRTY, limit - ptr);
if (dirty == NULL) {
break;
}
assert((dirty > ptr) && (dirty < limit));
ptr = scanDirtyCards(dirty, limit, ctx);
if (ptr == NULL) {
break;
}
assert((ptr > dirty) && (ptr < limit));
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
在前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文提到,在并行GC執行的過程中,如果一個對象被修改了,例如通過函數dvmSetFieldObject修改了一個對象的引用類型的成員變量,那么就會調用函數dvmMarkCard將Card Table中對應的字節的設置為GC_CARD_DIRTY。
函數scanGrayObjects通過函數memchr依次找到Card Table中包含GC_CARD_DIRTY值的內存塊,然后再使用函數scanDirtyCards該內存塊進行遍歷,以便找到那些有可能將該內存塊設置為GC_CARD_DIRTY的對象,并且對它們進行遍歷。
函數scanDirtyCards的實現如下所示:
~~~
const u1 *scanDirtyCards(const u1 *start, const u1 *end,
GcMarkContext *ctx)
{
const HeapBitmap *markBits = ctx->bitmap;
const u1 *card = start, *prevAddr = NULL;
while (card < end) {
if (*card != GC_CARD_DIRTY) {
return card;
}
const u1 *ptr = prevAddr ? prevAddr : (u1*)dvmAddrFromCard(card);
const u1 *limit = ptr + GC_CARD_SIZE;
while (ptr < limit) {
Object *obj = nextGrayObject(ptr, limit, markBits);
if (obj == NULL) {
break;
}
scanObject(obj, ctx);
ptr = (u1*)obj + ALIGN_UP(objectSize(obj), HB_OBJECT_ALIGNMENT);
}
if (ptr < limit) {
/* Ended within the current card, advance to the next card. */
++card;
prevAddr = NULL;
} else {
/* Ended past the current card, skip ahead. */
card = dvmCardFromAddr(ptr);
prevAddr = ptr;
}
}
return NULL;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數scanDirtyCards最外層的while循環依次遍歷`[start, end)`范圍內的Dirty Card。從前面[Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/41338251)一文可以知道,每GC_CARD_SIZE個對象共用一個Dirty Card,因此,對于每一個Dirty Card,函數scanDirtyCards內層的while循環都必須遍歷可能存在的GC_CARD_SIZE個對象,并且調用我們前面分析過的函數scanObject對它們引用的其它對象進行標記。
注意,在函數scanDirtyCards中,我們最開始知道的只有Dirty Card的地址,但是我們遍歷對象的時候需要的是對象地址,因此需要從Dirty Card地址計算出對象地址,這是通過函數dvmAddrFromCard來實現的。此外,我們還可以通過函數dvmCardFromAddr根據對象地址獲得其對應的Card地址。關于函數dvmAddrFromCard和dvmCardFromAddr的實現,可以參考前面[Dalvik虛擬機Java堆創建過程分析](http://blog.csdn.net/luoshengyang/article/details/41581063)一文。
取出一個Dirty Card對應的下一個對象可以通過函數nextGrayObject來實現。由于一個Card對應的GC_CARD_SIZE個對象在堆空間上都是連續的,因此,當我們從Dirty Card中取得前一個對象之后,就可以根據該對象的大小估算出下一個對象的可能地址。通過這種方式,可以加快從Dirty Card中取出對象的效率。
函數nextGrayObject的實現如下所示:
~~~
static Object *nextGrayObject(const u1 *base, const u1 *limit,
const HeapBitmap *markBits)
{
const u1 *ptr;
......
for (ptr = base; ptr < limit; ptr += HB_OBJECT_ALIGNMENT) {
if (dvmHeapBitmapIsObjectBitSet(markBits, ptr))
return (Object *)ptr;
}
return NULL;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
雖然每GC_CARD_SIZE個對象共用一個Dirty Card,但是這些對象不一定會存在。對于不存在的對象,我們是不需要標記它們所引用的對象的。因此,從Dirty Card取出下一個對象的時候,只需要調用函數dvmHeapBitmapIsObjectBitSet判斷它在Mark Bitmap中對應的位是否等于1,就可以知道該對象是否存在了。之所以能夠這樣做是因為,一個灰色對象要么是一個在并行GC過程中新創建或者新訪問的對象,要么是一個原來已經存在的對象,即在前一輪標記中已經在Mark Bitmap中標記為存活。無論是哪一種對象,如果它們在并行GC運行之后,仍然是存活的,那么要么是在新的根集中,要么是要原來已經被標記為存活的對象集合中。因此,不管是哪一種情況,都可以保證在并GC運行之后仍然存活的對象在Mark Bitmap中是被標記過的。現在我們遍歷灰色對象,僅僅是為將它們所引用的對象進行標記。換句話說,通過函數nextGrayObject的過濾,可以將那些雖然在并行GC過程中被修改過的但是在并行GC執行過后不再存活了的對象過濾掉,這樣就可以加快后面歸遞標記對象的過程。
這樣,當函數dvmHeapReScanMarkedObjects執行完成之后,那些在并行GC執行過程中產生的新根集對象和被重新引用的對象都會得到標記。
9. dvmHeapProcessReferences
函數dvmHeapProcessReferences用來處理引用類型的對象,它的實現如下所示:
~~~
void dvmHeapProcessReferences(Object **softReferences, bool clearSoftRefs,
Object **weakReferences,
Object **finalizerReferences,
Object **phantomReferences)
{
assert(softReferences != NULL);
assert(weakReferences != NULL);
assert(finalizerReferences != NULL);
assert(phantomReferences != NULL);
/*
* Unless we are in the zygote or required to clear soft
* references with white references, preserve some white
* referents.
*/
if (!gDvm.zygote && !clearSoftRefs) {
preserveSomeSoftReferences(softReferences);
}
/*
* Clear all remaining soft and weak references with white
* referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
* Preserve all white objects with finalize methods and schedule
* them for finalization.
*/
enqueueFinalizerReferences(finalizerReferences);
/*
* Clear all f-reachable soft and weak references with white
* referents.
*/
clearWhiteReferences(softReferences);
clearWhiteReferences(weakReferences);
/*
* Clear all phantom references with white referents.
*/
clearWhiteReferences(phantomReferences);
/*
* At this point all reference lists should be empty.
*/
assert(*softReferences == NULL);
assert(*weakReferences == NULL);
assert(*finalizerReferences == NULL);
assert(*phantomReferences == NULL);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
前面在標記對象的過程中,SoftReference、WeakReference、FinalizerReference和PhantomReference類型的引用對象分別被保存在了gcHeap->softReferences、gcHeap->weakReferences、gcHeap->finalizerReferences和gcHeap->phantomReferences四個列表中,函數dvmHeapProcessReferences的參數softReferences、weakReferences、finalizerReferences和phantomReferences分別指向這四個表中。
另外一個參數clearSoftRefs表示是否要全部回收SoftReference引用的對象。只有堆內存很緊張的時候,SoftReference引用的對象才會被回收。從前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文可以知道,在堆上分配對象的過程中遇到內存不足時,首先嘗試在不全部回收SoftReference引用的對象情況下進行GC。如果GC之后仍然是不能成功分配對象,最后才會再次進行GC,并且指定要回收SoftReference引用的對象。
如果不要求全部回收SoftReference引用的對象,即參數clearSoftRefs的值等于false,并且當前進程是應用程序進程,即gDvm.zygote的值等于false,那么就會保留部分SoftReference引用的對象。換句話說,如果當前進程是Zygote進程,或者要求全部回收SoftReference引用的對象,那么被SoftReference引用的對象就一個都不能保留。
保留部分SoftReference引用的對象是通過函數preserveSomeSoftReferences來實現的,如下所示:
~~~
static void preserveSomeSoftReferences(Object **list)
{
assert(list != NULL);
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
Object *clear = NULL;
size_t counter = 0;
while (*list != NULL) {
Object *ref = dequeuePendingReference(list);
Object *referent = dvmGetFieldObject(ref, referentOffset);
if (referent == NULL) {
/* Referent was cleared by the user during marking. */
continue;
}
bool marked = isMarked(referent, ctx);
if (!marked && ((++counter) & 1)) {
/* Referent is white and biased toward saving, mark it. */
markObject(referent, ctx);
marked = true;
}
if (!marked) {
/* Referent is white, queue it for clearing. */
enqueuePendingReference(ref, &clear);
}
}
*list = clear;
/*
* Restart the mark with the newly black references added to the
* root set.
*/
processMarkStack(ctx);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數preserveSomeSoftReferences依次遍歷保存在參數list描述的SoftReference引用對象列表,并且檢查它們所引用的對象。如果被引用對象存在,并且之前沒有被標記過,那么就隔一個就調用函數markObject來進行標記。而對于引用對象沒有被標記的引用,則重新被鏈入到參數list描述的SoftReference引用對象列表,以便后面可以進行處理。換句話說,Dalvik虛擬機在GC過程中保留SoftReference引用對象引用的對象的策略是每隔一個就保留一個,保留總數的一半。
由于被保留的對象可能又會引用其它對象,因此我們需要遞歸地標記被保留對象。在調用函數markObject標記被保留對象的過程中,被保留對象會被壓入到Mark Stack中,因此,這里我們只要調用前面分析過的函數processMarkStack就可以遞歸地標記被保留對象,直到它們直接或者間接引用的對象全部都被標記為止。
回到函數dvmHeapProcessReferences中,保留了部分SoftReference引用的對象之后,接下來就開始調用函數clearWhiteReferences清理剩余的被SoftReference引用的對象以及所有被WeakReference引用的對象。這些對象除了被SoftReference和WeakReference引用之外,再沒有被其它對象引用,也就是說它們接下來是要被回收的,因此我們將引用了它們的SoftReference和WeakReference稱為White Reference。
函數clearWhiteReferences的實現如下所示:
~~~
static void clearWhiteReferences(Object **list)
{
assert(list != NULL);
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
while (*list != NULL) {
Object *ref = dequeuePendingReference(list);
Object *referent = dvmGetFieldObject(ref, referentOffset);
if (referent != NULL && !isMarked(referent, ctx)) {
/* Referent is white, clear it. */
clearReference(ref);
if (isEnqueuable(ref)) {
enqueueReference(ref);
}
}
}
assert(*list == NULL);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
只有那些之前沒有被標記過的,并且被SoftReference和WeakReference引用的對象才會被處理。處理過程如下所示:
1. 調用函數clearReference函數切斷引用對象和被引用對象的關系,也就是將引用對象的成員變量referent設置為NULL。
2. 如果引用對象在創建的時候關聯有隊列,那么就調用函數enqueueReference將其加入到關聯隊列中去,以便應用程序可以知道哪些引用對象引用的對象被GC回收了。
再回到函數dvmHeapProcessReferences中,處理完成SoftReference和WeakReference引用之后。接著再調用函數enqueueFinalizerReferences處理FinalizerReference引用。
函數enqueueFinalizerReferences的實現如下所示:
~~~
static void enqueueFinalizerReferences(Object **list)
{
assert(list != NULL);
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
size_t referentOffset = gDvm.offJavaLangRefReference_referent;
size_t zombieOffset = gDvm.offJavaLangRefFinalizerReference_zombie;
bool hasEnqueued = false;
while (*list != NULL) {
Object *ref = dequeuePendingReference(list);
Object *referent = dvmGetFieldObject(ref, referentOffset);
if (referent != NULL && !isMarked(referent, ctx)) {
markObject(referent, ctx);
/* If the referent is non-null the reference must queuable. */
assert(isEnqueuable(ref));
dvmSetFieldObject(ref, zombieOffset, referent);
clearReference(ref);
enqueueReference(ref);
hasEnqueued = true;
}
}
if (hasEnqueued) {
processMarkStack(ctx);
}
assert(*list == NULL);
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數enqueueFinalizerReferences依次遍歷參數list描述的FinalizerReference列表,對于被FinalizerReference引用的,并且之前沒有被標記過的對象,執行以下操作:
1. 調用函數markObject對其進行標記。
2. 調用函數dvmSetFieldObject將被引用對象從FinalizerReference引用的成員變量referent轉移到成員變量zombie。
3. 調用函數clearReference將FinalizerReference引用的成員變量referent設置為NULL。
4. 調用函數enqueueReferece將FinalizerReference引用加入到關聯的列表。
執行完成上述四個步驟后,Dalvik虛擬機里面的Finalizer線程就可以調度執行被FinalizerReference引用的對象的成員函數finalize了。也正是由于被FinalizerReference引用的對象需要執行成員函數finalize,所以它們在沒有被回收之前,需要再標記一次。
與前面調用函數preserveSomeSoftReferences來保留部分被SoftReference引用的對象類似,在函數enqueueFinalizerReferences里被重新標記的對象,有可能引用了其它對象。因此,在函數enqueueFinalizerReferences的最后,需要調用函數processMarkStack來進行遞歸標記。
再回到函數dvmHeapProcessReferences中,這時候被FinalizerReference引用的對象就遞歸標記完成了。但是在遞歸中被標記的這些對象中,有些可能是SoftReference對象,有些也可能是WeakReference對象。對于這兩類對象,它所引用的對象是要回收的,因此,函數dvmHeapProcessReferences接下來要調用函數clearWhiteReferences對它們進行清理。
最后,函數dvmHeapProcessReferences調用函數clearWhiteReferences來清理被PhantomReference引用的對象。
從函數dvmHeapProcessReferences的執行過程就可以看出,在一次GC中,被SoftReference引用的對象有可能被回收,也有可能不被回收,定義有成員函數finalize的對象有一次機會不被回收,被WeakReference和PhantomReference引用的對象全部會被回收。
10. dvmHeapSweepSystemWeaks
函數dvmHeapSweepSystemWeaks用來回收在Dalvik虛擬機內部使用的類似被WeakReference引用的對象,它的實現如下所示:
~~~
void dvmHeapSweepSystemWeaks()
{
dvmGcDetachDeadInternedStrings(isUnmarkedObject);
dvmSweepMonitorList(&gDvm.monitorList, isUnmarkedObject);
sweepWeakJniGlobals();
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數dvmHeapSweepSystemWeaks回收的對象包括:
* A. 在字符串常量池中不再被引用的字符串。它們通過函數dvmGcDetachDeadInternedStrings來回收。
* B. 在gDvm.monitorList列表不再被引用的Monitor對象。它們通過函數dvmSweepMonitorList來回收。
* C. 在JNI中被WeakReference引用的全局對象,它們通過函數sweepWeakJniGlobals來回收。
11. dvmHeapSourceSwapBitmaps
函數dvmHeapSourceSwapBitmaps用來交換Live Bitmap和Mark Bitmap,它的實現如下所示:
~~~
void dvmHeapSourceSwapBitmaps()
{
HeapBitmap tmp = gHs->liveBits;
gHs->liveBits = gHs->markBits;
gHs->markBits = tmp;
}
~~~
這個函數定義在文件alvik/vm/alloc/HeapSource.cpp中。
在執行函數dvmHeapSourceSwapBitmaps的時候,在當前GC后還存活的對象在Mark Bitmap中對應的位都已經設置為1,而在上一次GC后還存活的對象在Live Bitmap中對應的位都已經被設置為1。由于Live Bitmap總是用來標記上一次GC后還存活的對象,因此,這里就將Mark Bitmap和Live Bitmap進行交換。
12. dvmHeapSweepUnmarkedObjects
函數dvmHeapSweepUnmarkedObjects用來清除不再被引用的對象,它的實現如下所示:
~~~
void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent,
size_t *numObjects, size_t *numBytes)
{
uintptr_t base[HEAP_SOURCE_MAX_HEAP_COUNT];
uintptr_t max[HEAP_SOURCE_MAX_HEAP_COUNT];
SweepContext ctx;
HeapBitmap *prevLive, *prevMark;
size_t numHeaps, numSweepHeaps;
numHeaps = dvmHeapSourceGetNumHeaps();
dvmHeapSourceGetRegions(base, max, numHeaps);
if (isPartial) {
assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == base[0]);
numSweepHeaps = 1;
} else {
numSweepHeaps = numHeaps;
}
ctx.numObjects = ctx.numBytes = 0;
ctx.isConcurrent = isConcurrent;
prevLive = dvmHeapSourceGetMarkBits();
prevMark = dvmHeapSourceGetLiveBits();
for (size_t i = 0; i < numSweepHeaps; ++i) {
dvmHeapBitmapSweepWalk(prevLive, prevMark, base[i], max[i],
sweepBitmapCallback, &ctx);
}
*numObjects = ctx.numObjects;
*numBytes = ctx.numBytes;
if (gDvm.allocProf.enabled) {
gDvm.allocProf.freeCount += ctx.numObjects;
gDvm.allocProf.freeSize += ctx.numBytes;
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
前面提到,當當前GC執行的是部分垃圾回收時,即參數isPartial等于true時,只有Active堆的垃圾會被回收。由于Active堆總是在base[0],所以函數dvmHeapSweepUnmarkedObjects在只執行部分垃圾回收時,將變量numSweepHeaps的值設置為1,就可以保證在后面的for循環只會遍歷和清除Active堆的垃圾。
此外,變量prevLive和preMark指向的分別是當前Mark Bitmap和Live Bitmap,但是由于在執行函數dvmHeapSweepUnmarkedObjects之前,Mark Bitmap和Live Bitmap已經被交換,因此,變量prevLive和preMark實際上指向的Bitmap描述的分別上次GC后仍然存活的對象和當前GC后仍然存活的對象。
準備工作完成之后,函數dvmHeapSweepUnmarkedObjects調用函數dvmHeapBitmapSweepWalk來遍歷上次GC后仍存活但是當前GC后不再存活的對象。這些對象正是要被回收的對象,它們通過函數sweepBitmapCallback來回收。
函數dvmHeapSweepUnmarkedObjects的實現如下所示:
~~~
void dvmHeapBitmapSweepWalk(const HeapBitmap *liveHb, const HeapBitmap *markHb,
uintptr_t base, uintptr_t max,
BitmapSweepCallback *callback, void *callbackArg)
{
......
void *pointerBuf[4 * HB_BITS_PER_WORD];
void **pb = pointerBuf;
size_t start = HB_OFFSET_TO_INDEX(base - liveHb->base);
size_t end = HB_OFFSET_TO_INDEX(max - liveHb->base);
unsigned long *live = liveHb->bits;
unsigned long *mark = markHb->bits;
for (size_t i = start; i <= end; i++) {
unsigned long garbage = live[i] & ~mark[i];
if (UNLIKELY(garbage != 0)) {
unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);
uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + liveHb->base;
while (garbage != 0) {
int shift = CLZ(garbage);
garbage &= ~(highBit >> shift);
*pb++ = (void *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);
}
/* Make sure that there are always enough slots available */
/* for an entire word of 1s. */
if (pb >= &pointerBuf[NELEM(pointerBuf) - HB_BITS_PER_WORD]) {
(*callback)(pb - pointerBuf, pointerBuf, callbackArg);
pb = pointerBuf;
}
}
}
if (pb > pointerBuf) {
(*callback)(pb - pointerBuf, pointerBuf, callbackArg);
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapBitmap.cpp中。
函數dvmHeapSweepUnmarkedObjects的實現邏輯很簡單,它在在參數liveGB描述的Bitmap中位等于1但是在參數markHb描述的Bitmap中位卻等于0的對象,也就是在上次GC后仍然存活的但是在當前GC后卻不存活的對象,并且將這些對象的地址收集在變量pointerBuf描述的數組中,最后調用參數callback指向的回調函數批量回收保存在該數組中的對象。
參數callback指向的回調函數為sweepBitmapCallback,它的實現如下所示:
~~~
static void sweepBitmapCallback(size_t numPtrs, void **ptrs, void *arg)
{
assert(arg != NULL);
SweepContext *ctx = (SweepContext *)arg;
if (ctx->isConcurrent) {
dvmLockHeap();
}
ctx->numBytes += dvmHeapSourceFreeList(numPtrs, ptrs);
ctx->numObjects += numPtrs;
if (ctx->isConcurrent) {
dvmUnlockHeap();
}
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
如果當前執行的并行GC,參照前面的圖1,此時Java堆是沒有被鎖定的,因此,函數sweepBitmapCallback在調用另外一個函數dvmHeapSourceFreeList來回收參數ptrs描述的Java堆內存時,需要先鎖定堆,并且在回收完成后解鎖堆。
函數dvmHeapSourceFreeList是真正進行內存回收的地方,它的實現如下所示:
~~~
size_t dvmHeapSourceFreeList(size_t numPtrs, void **ptrs)
{
......
Heap* heap = ptr2heap(gHs, *ptrs);
size_t numBytes = 0;
if (heap != NULL) {
mspace msp = heap->msp;
......
if (heap == gHs->heaps) {
// Count freed objects.
for (size_t i = 0; i < numPtrs; i++) {
......
countFree(heap, ptrs[i], &numBytes);
}
// Bulk free ptrs.
mspace_bulk_free(msp, ptrs, numPtrs);
} else {
// This is not an 'active heap'. Only do the accounting.
for (size_t i = 0; i < numPtrs; i++) {
......
countFree(heap, ptrs[i], &numBytes);
}
}
}
return numBytes;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
函數dvmHeapSourceFreeList首先調用函數計算要釋放的內存所屬的堆heap,即Active堆還是Zygote堆。Active堆保存在gHs->heaps指向的Heap數組的第一個元素,根據這個信息就可以知道堆heap是Active堆還是Zygote堆。
對于Active堆,函數dvmHeapSourceFreeList首先是對每一塊要釋放的內存調用函數countFree進行計賬,然后再調用C庫提供的接口mspace_build_free批量釋放參數ptrs指向的一系列內存塊。
對于Zygote堆,函數dvmHeapSourceFreeList僅僅是對每一塊要釋放的內存調用函數countFree進行計賬,但是并沒有真正釋放對應的內存塊。這是因為Zygote堆是Zygote進程和所有的應用程序進程共享。一旦某一個進程對它進行了寫類型的操作,那么就會導致對應的內存塊不再在Zygote進程和應用程序進程之間進行共享。這樣對Zygote堆的內存塊進行釋放反而會增加物理內存的開銷。
13. dvmHeapFinishMarkStep
函數dvmHeapFinishMarkStep用來執行GC清理工作,它的實現如下所示:
~~~
void dvmHeapFinishMarkStep()
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
/* The mark bits are now not needed.
*/
dvmHeapSourceZeroMarkBitmap();
/* Clean up everything else associated with the marking process.
*/
destroyMarkStack(&ctx->stack);
ctx->finger = NULL;
}
~~~
這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。
函數dvmHeapFinishMarkStep做三個清理工作。一是調用函數dvmHeapSourceZeroMarkBitmap重置Mark Bitmap;二是調用函數destroyMarkStack銷毀Mark Stack;三是將遞歸標記對象過程中使用到的finger值設置為NULL。
14. dvmHeapSourceGrowForUtilization
函數dvmHeapSourceGrowForUtilization用來將堆的大小設置為理想值,它的實現如下所示:
~~~
/*
* Given the current contents of the active heap, increase the allowed
* heap footprint to match the target utilization ratio. This
* should only be called immediately after a full mark/sweep.
*/
void dvmHeapSourceGrowForUtilization()
{
HS_BOILERPLATE();
HeapSource *hs = gHs;
Heap* heap = hs2heap(hs);
/* Use the current target utilization ratio to determine the
* ideal heap size based on the size of the live set.
* Note that only the active heap plays any part in this.
*
* Avoid letting the old heaps influence the target free size,
* because they may be full of objects that aren't actually
* in the working set. Just look at the allocated size of
* the current heap.
*/
size_t currentHeapUsed = heap->bytesAllocated;
size_t targetHeapSize = getUtilizationTarget(hs, currentHeapUsed);
/* The ideal size includes the old heaps; add overhead so that
* it can be immediately subtracted again in setIdealFootprint().
* If the target heap size would exceed the max, setIdealFootprint()
* will clamp it to a legal value.
*/
size_t overhead = getSoftFootprint(false);
setIdealFootprint(targetHeapSize + overhead);
size_t freeBytes = getAllocLimit(hs);
if (freeBytes < CONCURRENT_MIN_FREE) {
/* Not enough free memory to allow a concurrent GC. */
heap->concurrentStartBytes = SIZE_MAX;
} else {
heap->concurrentStartBytes = freeBytes - CONCURRENT_START;
}
/* Mark that we need to run finalizers and update the native watermarks
* next time we attempt to register a native allocation.
*/
gHs->nativeNeedToRunFinalization = true;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
每次GC執行完成之后,都需要根據預先設置的目標堆利用率和已經分配出去的內存字節數計算得到理想的堆大小。注意,已經分配出去的內存字節數只考慮在Active堆上分配出去的字節數。得到了Active堆已經分配出去的字節數currentHeapUsed之后,就可以調用函數getUtilizationTarget來計算Active堆的理想大小targetHeapSize了。
函數getUtilizationTarget的實現如下所示:
~~~
/*
* Given the size of a live set, returns the ideal heap size given
* the current target utilization and MIN/MAX values.
*/
static size_t getUtilizationTarget(const HeapSource* hs, size_t liveSize)
{
/* Use the current target utilization ratio to determine the
* ideal heap size based on the size of the live set.
*/
size_t targetSize = (liveSize / hs->targetUtilization) * HEAP_UTILIZATION_MAX;
/* Cap the amount of free space, though, so we don't end up
* with, e.g., 8MB of free space when the live set size hits 8MB.
*/
if (targetSize > liveSize + hs->maxFree) {
targetSize = liveSize + hs->maxFree;
} else if (targetSize < liveSize + hs->minFree) {
targetSize = liveSize + hs->minFree;
}
return targetSize;
}
~~~
這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。
堆的目標利用率保存在hs->targetUtilization,它是以HEAP_UTILIZATION_MAX為基數計算出來得到的一個百分比,因此這里計算堆的理想大小時,需要乘以HEAP_UTILIZATION_MAX。
計算出來的堆理想大小targetSize要滿足空閑內存不能大于預先設定的最大值(hs->maxFree)以及不能小于預先設定的最小值(hs->minFree)。否則的話,就要進行相應的調整。
回到函數dvmHeapSourceGrowForUtilization中,得到了Active堆的理想大小targetHeapSize之后,還要以參數false調用函數getSoftFootprint得到Zygote堆的大小overhead。將targetHeapSize和overhead相加,將得到的結果調用函數setIdealFootprint設置Java堆的大小。由此我們就可以知道,函數setIdealFootprint設置的整個Java堆的大小,而不是Active堆的大小,因此前面需要得到Zygote堆的大小。
函數dvmHeapSourceGrowForUtilization接下來調用函數getAllocLimit得到堆當前允許分配的最大值freeBytes,然后計算并行GC觸發的條件。從前面[Dalvik虛擬機為新創建對象分配內存的過程分析](http://blog.csdn.net/luoshengyang/article/details/41688319)一文可以知道,當Active堆heap當前已經分配的大小超過heap->concurrentStartBytes時,就會觸發并行GC。
計算并行GC觸發條件時,需要用到CONCURRENT_MIN_FREE和CONCURRENT_START兩個值,它們的定義如下所示:
~~~
/* Start a concurrent collection when free memory falls under this
* many bytes.
*/
#define CONCURRENT_START (128 << 10)
/* The next GC will not be concurrent when free memory after a GC is
* under this many bytes.
*/
#define CONCURRENT_MIN_FREE (CONCURRENT_START + (128 << 10))
~~~
這兩個宏定義在文件dalvik/vm/alloc/HeapSource.cpp中。
也就是說,CONCURRENT_MIN_FREE定義為256K,而CONCURRENT_START定義為128K。
由此我們就可以知道,當Active堆允許分配的內存小于256K時,禁止執行并行GC,而當Active堆允許分配的內存大于等于256K,并且剩余的空閑內存小于128K,就會觸發并行GC。
函數dvmHeapSourceGrowForUtilization最后是將gHs->nativeNeedToRunFinalization的值設置為true,這樣當以后我們調用dalvik.system.VMRuntime類的成員函數registerNativeAllocation注冊Native內存分配數時,就會觸發java.lang.System類的靜態成員函數runFinalization被調用,從而使得那些定義了成員函數finalize又即將被回收的對象執行成員函數finalize。
至此,我們就分析完成Dalvik虛擬機的垃圾收集過程了。Android 5.0之后,Dalvik虛擬機已經被ART運行時替換了。我們這里分析Dalvik虛擬機垃圾收集過程的目的,是為了能夠更好地理解ART運行時的垃圾收集機制,因為兩者有很多相通的東西。同時,Dalvik虛擬機的垃圾收集過程要比ART運行時的垃圾收集過程簡單。因此,先學習Dalvik虛擬機的垃圾收集過程,可以使得我們循序漸進地更好學習ART運行時的垃圾收集過程。在接下來的一系列文章,我們就將開始分析ART運行時的垃圾收集機制,敬請關注!
- 前言
- 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)的過程分析