![Dalvik虛擬機垃圾收集(GC)過程分析_第1頁](http://file4.renrendoc.com/view/f8b27568143ee52ba1d7eb3bd39733b3/f8b27568143ee52ba1d7eb3bd39733b31.gif)
![Dalvik虛擬機垃圾收集(GC)過程分析_第2頁](http://file4.renrendoc.com/view/f8b27568143ee52ba1d7eb3bd39733b3/f8b27568143ee52ba1d7eb3bd39733b32.gif)
![Dalvik虛擬機垃圾收集(GC)過程分析_第3頁](http://file4.renrendoc.com/view/f8b27568143ee52ba1d7eb3bd39733b3/f8b27568143ee52ba1d7eb3bd39733b33.gif)
![Dalvik虛擬機垃圾收集(GC)過程分析_第4頁](http://file4.renrendoc.com/view/f8b27568143ee52ba1d7eb3bd39733b3/f8b27568143ee52ba1d7eb3bd39733b34.gif)
![Dalvik虛擬機垃圾收集(GC)過程分析_第5頁](http://file4.renrendoc.com/view/f8b27568143ee52ba1d7eb3bd39733b3/f8b27568143ee52ba1d7eb3bd39733b35.gif)
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、.:.;Dalvik虛擬機渣滓搜集GC過程分析前面我們分析了Dalvik虛擬機堆的創(chuàng)建過程,以及Java對象在堆上的分配過程。這些知識都是了解Dalvik虛擬機渣滓搜集過程的根底。渣滓搜集是一個復雜的過程,它要將那些不再被援用的對象進展回收。一方面要求Dalvik虛擬機可以標志出哪些對象是不再被援用的。另一方面要求Dalvik虛擬機盡快地回收內存,防止運用程序長時間停頓。本文就將詳細分析Dalvik虛擬機是如何處理上述問題完成渣滓搜集過程的。Dalvik虛擬機運用Mark-Sweep算法來進展渣滓搜集。顧名思義,Mark-Sweep算法就是為Mark和Sweep兩個階段進展渣滓回收。其中,Ma
2、rk階段從根集Root Set開場,遞歸地標志出當前一切被援用的對象,而Sweep階段擔任回收那些沒有被援用的對象。在分析Dalvik虛擬機運用的Mark-Sweep算法之前,我們先來了解一下什么情況下會觸發(fā)GC。 Dalvik虛擬機在三種情況下會觸發(fā)四種類型的GC。每一種類型GC運用一個GcSpec構造體來描畫,它的定義如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片struct GcSpec /* If true, only the application heap is threatened. */ bool isPartial; /* If tr
3、ue, 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時,表示僅僅回收Ac
4、tive堆的渣滓;為false時,表示同時回收Active堆和Zygote堆的渣滓。 isConcurrent: 為true時,表示執(zhí)行并行GC;為false時,表示執(zhí)行非并行GC。 doPreserve: 為true時,表示在執(zhí)行GC的過程中,不回收軟援用援用的對象;為false時,表示在執(zhí)行GC的過程中,回收軟援用援用的對象。 reason: 一個描畫性的字符串。 Davlik虛擬機定義了四種類的GC,如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片/* Not enough space for an ordinary Object to be al
5、located. */ 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 throwi
6、ng an OOM. */ extern const GcSpec *GC_BEFORE_OOM; 這四個全局變量聲明在文件dalvik/vm/alloc/Heap.h中。 它們的含義如下所示: GC_FOR_MALLOC: 表示是在堆上分配對象時內存缺乏觸發(fā)的GC。 GC_CONCURRENT: 表示是在已分配內存到達一定量之后觸發(fā)的GC。 GC_EXPLICIT: 表示是運用程序調用System.gc、VMRuntime.gc接口或者收到SIGUSR1信號時觸發(fā)的GC。 GC_BEFORE_OOM: 表示是在預備拋OOM異常之前進展的最后努力而觸發(fā)的GC。 實踐上,GC_FOR_MALLO
7、C、GC_CONCURRENT和GC_BEFORE_OOM三種類型的GC都是在分配對象的過程觸發(fā)的。 在前面一文,我們提到,Dalvik虛擬機在Java堆上分配對象的時候,在碰到分配失敗的情況,會嘗試調用函數gcForMalloc進展渣滓回收。 函數gcForMalloc的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片static void gcForMalloc(bool clearSoftReferences) const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_M
8、ALLOC; dvmCollectGarbageInternal(spec); 這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。 參數clearSOftRefereces表示能否要對軟援用援用的對象進展回收。假設要對軟援用援用的對象進展回收,那么就闡明當前內存是非常緊張的了,因此,這時候執(zhí)行的就是GC_BEFORE_OOM類型的GC。否那么的話,執(zhí)行的就是GC_FOR_MALLOC類型的GC。它們都是經過調用函數dvmCollectGarbageInternal來執(zhí)行的。 在前面一文,我們也提到,當Dalvik虛擬機勝利地在堆上分配一個對象之后,會檢查一下當前分配的內存能
9、否超出一個閥值,如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片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);
10、 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。假設大于,那么就會經過條件變量g
11、Hs-gcThreadCond喚醒GC線程進展渣滓回收。預設的閥值heap-concurrentStartBytes是一個比指定的堆最小空閑內存小128K的數值。也就是說,當堆的空閑內缺乏時,就會觸發(fā)GC_CONCURRENT類型的GC。 GC線程是Dalvik虛擬機啟動的過程中創(chuàng)建的,它的執(zhí)行體函數是gcDaemonThread,實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片static void *gcDaemonThread(void* arg) dvmChangeStatus(NULL, THREAD_VMWAIT); dvmLockMut
12、ex(&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
13、 = 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(NUL
14、L, 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毫秒內,都沒有得到執(zhí)行GC的通知,那么它就調用函數trimHeaps對Java堆進展裁剪,以便可以將堆上的一些沒有運用到的內存交還給內核。函數trim
15、Heaps的實現可以參考前面一文。否那么的話,就會調用函數dvmCollectGarbageInternal進展類型為GC_CONCURRENT的GC。 留意,函數gcDaemonThread在調用函數dvmCollectGarbageInternal進展類型為GC_CONCURRENT的GC之前,會先調用函數dvmLockHeap來鎖定堆的。等到GC執(zhí)行終了,再調用函數dvmUnlockHeap來解除對堆的鎖定。這與函數gcForMalloc調用函數dvmCollectGarbageInternal進展類型為GC_FOR_MALLOC和GC_CONCURRENT的GC是一樣的。只不過,對堆進
16、展鎖定和解鎖的操作是在調用堆棧上的函數dvmMalloc進展的,詳細可以參考前面一文。 當運用程序調用System.gc、VMRuntime.gc接口,或者接納到SIGUSR1信號時,最終會調用到函數dvmCollectGarbage,它的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片void dvmCollectGarbage() if (gDvm.disableExplicitGc) return; dvmLockHeap(); dvmWaitForConcurrentGcToComplete(); dvmCollectGarbageInter
17、nal(GC_EXPLICIT); dvmUnlockHeap(); 這個函數定義在文件dalvik/vm/alloc/Alloc.cpp中。 假設Davik虛擬機在啟動的時候,經過-XX:+DisableExplicitGC選項禁用了顯式GC,那么函數dvmCollectGarbage什么也不做就前往了。這意味著Dalvik虛擬機能夠會不支持運用程序顯式的GC懇求。 一旦Dalvik虛擬機支持顯式GC,那么函數dvmCollectGarbage就會先鎖定堆,并且等待有能夠正在執(zhí)行的GC_CONCURRENT類型的GC完成之后,再調用函數dvmCollectGarbageInternal進展類
18、型為GC_EXPLICIT的GC。 以上就是三種情況下會觸發(fā)四種類型的GC。有了這個背景知識之后 ,我們接下來就開場分析Dalvik虛擬機運用的Mark-Sweep算法,整個過程如圖1所示:圖1 Dalvik虛擬機渣滓搜集過程 Dalvik虛擬機支持非并行和并行兩種GC。在圖1中,左邊是非并行GC的執(zhí)行過程,而右邊是并行GC的執(zhí)行過程。它們的總體流程是類似的,主要差別在于前者在執(zhí)行的過程中不斷是掛起非GC線程的,而后者是有條件地掛起非GC線程。 由前面的分析可以知道,無論是并行GC,還是非并行GC,它們都是經過函數dvmCollectGarbageInternal來執(zhí)行的。函數dvmColle
19、ctGarbageInternal的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片void dvmCollectGarbageInternal(const GcSpec* spec) if (gcHeap-gcRunning) return; gcHeap-gcRunning = true; dvmSuspendAllThreads(SUSPEND_FOR_GC); if (!dvmHeapBeginMarkStep(spec-isPartial) dvmAbort(); dvmHeapMarkRootSet(); if (spec-isConcu
20、rrent) dvmClearCardTable(); dvmUnlockHeap(); dvmResumeAllThreads(SUSPEND_FOR_GC); dvmHeapScanMarkedObjects(); if (spec-isConcurrent) dvmLockHeap(); dvmSuspendAllThreads(SUSPEND_FOR_GC); dvmHeapReMarkRootSet(); dvmHeapReScanMarkedObjects(); dvmHeapProcessReferences(&gcHeap-softReferences, spec-doPres
21、erve = false, &gcHeap-weakReferences, &gcHeap-finalizerReferences, &gcHeap-phantomReferences); dvmHeapSweepSystemWeaks(); dvmHeapSourceSwapBitmaps(); if (spec-isConcurrent) dvmUnlockHeap(); dvmResumeAllThreads(SUSPEND_FOR_GC); dvmHeapSweepUnmarkedObjects(spec-isPartial, spec-isConcurrent, &numObject
22、sFreed, &numBytesFreed); dvmHeapFinishMarkStep(); if (spec-isConcurrent) dvmLockHeap(); dvmHeapSourceGrowForUtilization(); gcHeap-gcRunning = false; if (spec-isConcurrent) dvmBroadcastCond(&gDvm.gcHeapCond); if (!spec-isConcurrent) dvmResumeAllThreads(SUSPEND_FOR_GC); dvmEnqueueClearedReferences(&gD
23、vm.gcHeap-clearedReferences); 這個函數定義在文件dalvik/vm/alloc/Heap.cpp中。 前面提到,在調用函數dvmCollectGarbageInternal之前,堆是曾經被鎖定了的,因此這時候任何需求堆上分配對象的線程都會被掛起。留意,這不會影響到那些不需求在堆上分配對象的線程。 在圖1中顯示的GC流程中,除了第一步的Lock Heap和最后一步的Unlock Heap,都對應于函數dvmCollectGarbageInternal的實現,它的執(zhí)行邏輯如下所示: 第1步到第3步用于并行和非并行GC: 1. 調用函數dvmSuspendAllThre
24、ads掛起一切的線程,以免它們干擾GC。 2. 調用函數dvmHeapBeginMarkStep初始化Mark Stack,并且設定好GC范圍。關于Mark Stack的引見,可以參考前面一文。 3. 調用函數dvmHeapMarkRootSet標志根集對象。 第4到第6步用于并行GC: 4. 調用函數dvmClearCardTable清理Card Table。關于Card Table的引見,可以參考前面一文。由于接下來我們將會喚醒第1步掛起的線程。并且運用這個Card Table來記錄那些在GC過程中被修正的對象。 5. 調用函數dvmUnlock解鎖堆。這個是針對調用函數dvmCollec
25、tGarbageInternal執(zhí)行GC前的堆鎖定操作。 6. 調用函數dvmResumeAllThreads喚醒第1步掛起的線程。 第7步用于并行和非并行GC: 7. 調用函數dvmHeapScanMarkedObjects從第3步獲得的根集對象開場,歸遞標志一切被根集對象援用的對象。 第8步到第11步用于并行GC: 8. 調用函數dvmLockHeap重新鎖定堆。這個是針對前面第5步的操作。 9. 調用函數dvmSuspendAllThreads重新掛起一切的線程。這個是針對前面第6步的操作。 10. 調用函數dvmHeapReMarkRootSet更新根集對象。由于有能夠在第4步到第6步
26、的執(zhí)行過程中,有線程創(chuàng)建了新的根集對象。 11. 調用函數dvmHeapReScanMarkedObjects歸遞標志那些在第4步到第6步的執(zhí)行過程中被修正的對象。這些對象記錄在Card Table中。 第12步到第14步用于并行和非并行GC: 12. 調用函數dvmHeapProcessReferences處置那些被軟援用Soft Reference、弱援用Weak Reference和影子援用Phantom Reference援用的對象,以及重寫了finalize方法的對象。這些對象都是需求特殊處置的。 13. 調用函數dvmHeapSweepSystemWeaks回收系統(tǒng)內部運用的那些被
27、弱援用援用的對象。 14. 調用函數dvmHeapSourceSwapBitmaps交換Live Bitmap和Mark Bitmap。執(zhí)行了前面的13步之后,一切還被援用的對象在Mark Bitmap中的bit都被設置為1。而Live Bitmap記錄的是當前GC前還被援用著的對象。經過交換這兩個Bitmap,就可以使得當前GC完成之后,使得Live Bitmap記錄的是下次GC前還被援用著的對象。 第15步和第16步用于并行GC: 15. 調用函數dvmUnlock解鎖堆。這個是針對前面第8步的操作。 16. 調用函數dvmResumeAllThreads喚醒第9步掛起的線程。 第17步和
28、第18步用于并行和非并行GC: 17. 調用函數dvmHeapSweepUnmarkedObjects回收那些沒有被援用的對象。沒有被援用的對象就是那些在執(zhí)行第14步之前,在Live Bitmap中的bit設置為1,但是在Mark Bitmap中的bit設置為0的對象。 18. 調用函數dvmHeapFinishMarkStep重置Mark Bitmap以及Mark Stack。這個是針對前面第2步的操作。 第19步用于并行GC: 19. 調用函數dvmLockHeap重新鎖定堆。這個是針對前面第15步的操作。 第20步用于并行和非并行GC: 20. 調用函數dvmHeapSourceGrow
29、ForUtilization根據設置的堆目的利用率調整堆的大小。 第21步用于并行GC: 21. 調用函數dvmBroadcastCond喚醒那些等待GC執(zhí)行完成再在堆上分配對象的線程。 第22步用于非并行GC: 22. 調用函數dvmResumeAllThreads喚醒第1步掛起的線程。 第23步用到并行和非并行GC: 23. 調用函數dvmEnqueueClearedReferences將那些目的對象曾經被回收了的援用對象添加到相應的Java隊列中去,以便運用程序可以知道哪些援用援用的對象曾經被回收了。 以上就是并行和非并行GC的執(zhí)行總體流程,它們的主要區(qū)別在于,前者在GC過程中,有條件地
30、掛起和喚醒非GC線程,而后者在執(zhí)行GC的過程中,不斷都是掛起非GC線程的。并行GC經過有條件地掛起和喚醒非GC線程,就可以使得運用程序獲得更好的呼應性。但是我們也應該看到,并行GC需求多執(zhí)行一次標志根集對象以及遞歸標志那些在GC過程被訪問了的對象的操作。也就是說,并行GC在運用得運用程序獲得更好的呼應性的同時,也需求破費更多的CPU資源。 為了更深化地了解GC的執(zhí)行過程,接下來我們再詳細分析在上述的23步中涉及到的重要函數。 1. dvmSuspendAllThreads 函數dvmSuspendAllThreads用來掛起Dalvik虛擬機中除當前線程之外的其它線程,它的實現如下所示:cpp
31、 view plain copy 在CODE上查看代碼片派生到我的代碼片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)
32、 continue; /* debugger events dont 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); unlockThreadSu
33、spendCount(); for (thread = gDvm.threadList; thread != NULL; thread = thread-next) if (thread = self) continue; /* debugger events dont suspend JDWP thread */ if (why = SUSPEND_FOR_DEBUG | why = SUSPEND_FOR_DEBUG_EVENT) & thread-handle = dvmJdwpGetDebugThread(gDvm.jdwpState) continue; /* wait for th
34、e other thread to see the pending suspend */ waitForThreadSuspend(self, thread); dvmUnlockThreadList(); unlockThreadSuspend(); 這個函數定義在文件dalvik/vm/Thread.cpp中。 在以下三種情況下,當前線程需求掛起其它線程: 1. 執(zhí)行GC。 2. 調試器懇求。 3. 發(fā)生調試事件,如斷點命中。 而且,上述三種情況有能夠是同時發(fā)生的。例如,一個線程在分配對象的過程中發(fā)生GC的同時,調試器也剛好懇求掛起一切線程。這時候就需求保證每一個掛起操作都是順序執(zhí)行的,即
35、要對掛起線程的操作進展加鎖。這是經過調用函數函數lockThreadSuspend來實現的。 函數lockThreadSuspend嘗試獲取gDvm._threadSuspendLock鎖。假設獲取失敗,就闡明有其它線程也正在懇求掛起Dalvik虛擬機中的線程,包括當前線程。每一個Dalvik虛擬機線程都有一個Suspend Count計數,每當它們都掛起的時候,對應的Suspend Count計數就會加1,而當被喚醒時,對應的Suspend Count計數就會減1。在獲取gDvm._threadSuspendLock鎖失敗的情況下,當前線程按照一定的時間間隔檢查本人的Suspend Coun
36、t,直到本人的Suspend Count等于0,并且能勝利獲取gDvm._threadSuspendLock鎖為止。這樣就可以保證每一個掛起Dalvik虛擬機線程的懇求都可以得到順序執(zhí)行。 從函數lockThreadSuspend前往之后,就闡明當前線程可以執(zhí)行掛起其它線程的操作了。它首先要做的第一件事情是遍歷Dalvik虛擬機線程列表,并且調用函數dvmAddToSuspendCounts將列表里面的每一個線程對應的Suspend Count都添加1,但是除了當前線程之外。留意,當掛起線程的懇求是調試器發(fā)出或者是由調試事件觸發(fā)的時候,Dalvik虛擬機中的JDWP線程是不可以掛起的,由于JD
37、WP線程掛起來之后,就沒法調試了。在這種情況下,也不能將JDWP線程對應的Suspend Count都添加1。 經過調用函數dvmJdwpGetDebugThread可以獲得JDWP線程句柄,用這個句柄和Dalvik虛擬機線程列表中的線程句柄相比,就可以知道當前遍歷的線程能否是JDWP線程。同時,經過參數why可以知道當掛起線程的懇求能否是由調試器發(fā)出的或者由調試事件觸發(fā)的, 留意,在添加被掛起線程的Suspend Count計數之前,必需求獲取gDvm.threadSuspendCountLock鎖。這個鎖的獲取和釋放可以經過函數lockThreadSuspendCount和unlockTh
38、readSuspendCount完成。 將被掛起線程的Suspend Count計數都添加1之后,接下來就是等待被掛起線程自愿將本人掛起來了。這是經過函數waitForThreadSuspend來實現。當一個線程自愿將本人掛起來的時候,會將本人的形狀設置為非運轉形狀THREAD_RUNNING,這樣函數waitForThreadSuspend經過不斷地檢查一個線程的形狀能否處于非運轉形狀就可以知道它能否曾經掛起來了。 那么,一個線程在什么情況才會自愿將本人掛起來呢?一個線程在執(zhí)行的過程中,會在適宜的時候檢查本人的Suspend Count計數。一旦該計數值不等于0,那么它就知道有線程懇求掛起本
39、人,因此它就會很配合地將本人的形狀設置為非運轉的,并且將本人掛起來。例如,當一個線程經過解釋器執(zhí)行代碼時,就會周期性地檢查本人的Suspend Count能否等于0。這里說的周期性,實踐上就是碰到IF指令、GOTO指令、SWITCH指令、RETURN指令和THROW指令等時。 2. dvmHeapBeginMarkStep 函數dvmHeapBeginMarkStep用來初始化堆的標志范圍和Mark Stack,它的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片bool dvmHeapBeginMarkStep(bool isPartial) Gc
40、MarkContext *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
41、Stack,ctx-finger描畫的是一個標志指紋,在遞歸標志對象時會用到,ctx-immuneLimit用來限定堆的標志范圍。 函數dvmHeapBeginMarkStep調用另外一個函數createMarkStack來初始化Mark Stack,它的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片static bool createMarkStack(GcMarkStack *stack) assert(stack != NULL); size_t length = dvmHeapSourceGetIdealFootprint() * size
42、of(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來獲得。在堆上分配的
43、對象,都是從Object類承繼下來的,因此,每一個對象占用的最小內存是sizeof(Object)。由于在為對象分配內存時,還需求額外的內存來保管管理信息,例照實踐分配給對象的內存字節(jié)數。這些額外的管理信息占用的內存字節(jié)數經過宏HEAP_SOURCE_CHUNK_OVERHEAD來描畫。 由于Mark Stack實踐上是一個Object指針數組,因此有了上面的信息之后,我們就可以計算出Mark Stack的長度length。Mark Stack運用的內存塊在Dalvik虛擬機啟動的時候就創(chuàng)建好的,詳細參考前面一文,起始地址位于stack-base中。由于這塊內存開場的時候被函數madvice設
44、置為MADV_DONTNEED,因此這里要將它重新設置為MADV_NORMAL,以便可以通知內核,這塊內存要開場運用了。 Mark Stack的棧頂stack-top指向的是Mark Stack內存塊的起始位置,以后就會從這個位置開場由小到大增長。 回到函數dvmHeapBeginMarkStep中,ctx-immuneLimit記錄的實踐上是堆的起始標志位置。在此位置之前的對象,都不會被GC。這個位置是經過函數dvmHeapSourceGetImmuneLimit來確定的,它的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片void *dvmHea
45、pSourceGetImmuneLimit(bool isPartial) if (isPartial) return hs2heap(gHs)-base; else return NULL; 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 當參數isPartial等于true時,函數dvmHeapSourceGetImmuneLimit前往的是Active堆的起始位置,否那么的話就前往NULL值。也就是說,假設當前執(zhí)行的GC只需求回收部分渣滓,那么就只回收Active堆的渣滓,否那么的話,就同時回收Active堆和Zygote堆的渣滓。 3. dvmHea
46、pMarkRootSet 函數dvmHeapMarkRootSet用來的標志根集對象,它的實現如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片/* 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 v
47、ars) * - 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 stri
48、ng table * - Primitive classes * - Special objects * - gDvm.outOfMemoryObj * - Objects in debugger object registry * * Dont need: * - Native stack (for in-progress stuff in the VM) * - The TrackedAlloc stuff watches all native VM references. */ void dvmHeapMarkRootSet() GcHeap *gcHeap = gDvm.gcHeap;
49、 dvmMarkImmuneObjects(gcHeap-markContext.immuneLimit); dvmVisitRoots(rootMarkObjectVisitor, &gcHeap-markContext); 這個函數定義在文件dalvik/vm/alloc/MarkSweep.cpp中。 經過注釋我們可以看到根集對象都包含了哪些對象。總的來說,就是包含兩大類對象。一類是Dalvik虛擬機內部運用的全局對象,另一類是運用程序正在運用的對象。前者會維護在內部的一些數據構造中,例如Hash表,后者維護在調用棧中。對這些根集對象進展標志都是經過函數dvmVisitRoots和回調函
50、數rootMarkObjectVisitor進展的。 此外,我們還需求將不在堆回收范圍內的對象也看作是根集對象,以便后面可以運用一致的方法來遍歷這兩類對象所援用的其它對象。標志不在堆回收范圍內的對象是經過函數dvmMarkImmuneObjects來實現的,如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片void dvmMarkImmuneObjects(const char *immuneLimit) for (size_t i = 1; i numHeaps; +i) if (gHs-heapsi.base heapsi.limit heapsi.b
51、ase - 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-heapsi.l
52、imit - gHs-heapsi.base); /* Do the copy. */ memcpy(dst, src, length); /* Make sure max points to the address of the highest set bit. */ if (gHs-markBits.max heapsi.limit) gHs-markBits.max = (uintptr_t)gHs-heapsi.limit; 這個函數定義在文件dalvik/vm/alloc/HeapSource.cpp中。 從前面分析的函數dvmHeapSourceGetImmuneLimit可以知道
53、,參數immuneList要么是等于Zygote堆的最大地址值,要么是等于NULL。這取決于當前GC要執(zhí)行的是全部渣滓回收還是部分渣滓回收。 函數dvmMarkImmuneObjects是在當前GC執(zhí)行之前調用的,這意味著當前存活的對象都標志在Live Bitmap中。如今函數dvmMarkImmuneObjects要做的就是將不在回收范圍內的對象的標志位從Live Bitmap拷貝到Mark Bitmap去。詳細做法就是分別遍歷Active堆和Zygote堆,假設它們處于不回范圍中,那么就對里面的對象在Live Bitmap中對應的內存塊拷貝到Mark Bitmap的對應位置去。 計算一個對
54、象在一個Bitmap的標志位所在的內存塊是經過宏HB_OFFSET_TO_INDEX和HB_OFFSET_TO_BYTE_INDEX來實現的。這兩個宏的詳細實現可以參考前面一文。 回到函數dvmHeapMarkRootSet中,我們繼續(xù)分析函數dvmVisitRoots的實現,如下所示:cpp view plain copy 在CODE上查看代碼片派生到我的代碼片void dvmVisitRoots(RootVisitor *visitor, void *arg) assert(visitor != NULL); visitHashTable(visitor, gDvm.loadedClass
55、es, 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);
56、 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);
57、 (*visitor)(&gDvm.outOfMemoryObj, 0, ROOT_VM_INTERNAL, arg); (*visitor)(&gDernalErrorObj, 0, ROOT_VM_INTERNAL, arg); (*visitor)(&gDvm.noClassDefFoundErrorObj, 0, ROOT_VM_INTERNAL, arg); 這個函數定義在文件dalvik/vm/alloc/Visit.cpp。 參數visitor指向的是函數rootMarkObjectVisitor,它擔任將根集對象在Mark Bitmap中的位設置為1。我們后面再分析
58、這個函數的實現。 以下對象將被視為根集對象: 1. 被加載到Dalvik虛擬機的類對象。這些類對象緩存在gDvm.loadedClasses指向的一個Hash表中,經過函數visitHashTable來遍歷和標志。 2. 在Dalvik虛擬機內部創(chuàng)建的原子類,例如Double、Boolean類等,經過函數visitPrimitiveTypes來遍歷和標志。 3. 注冊在調試器的對象。這些對象保管在gDvm.dbgRegistry指向的一個Hash表中,經過函數visitHashTable來遍歷和標志。 4. 字符串常量池中的String對象。這些對象保管保管在gDvm.literalStrin
59、gs指向的一個Hash表中,經過函數visitHashTable來遍歷和標志。 5. 在JNI中創(chuàng)建的全局援用對象所援用的對象。這些被援用對象保管在gDvm.jniGlobalRefTable指向的一個間接援用表中,經過函數visitIndirectRefTable來遍歷和標志。 6. 在JNI中經過GetStringUTFChars、GetByteArrayElements等接口訪問字符串或者數組時被Pin住的對象。這些對象保管在gDvm.jniPinRefTable指向的一個援用表中,經過函數visitReferenceTable來遍歷和標志。 7. 當前位于Davlik虛擬機線程的調用棧
60、的對象。這些對象記錄在棧幀中,經過函數visitThreads來遍歷和標志。 8. 在Dalvik虛擬機內部創(chuàng)建的OutOfMemory異常對象,這個對象保管在gDvm.outOfMemoryObj中。 9. 在Dalvik虛擬機內部創(chuàng)建的InternalError異常對象,這個對象保管在gDernalErrorObj中。 10. 在Dalvik虛擬機內部創(chuàng)建的N oClassDefFoundError異常對象,這個對象保管在gDvm.noClassDefFoundErrorObj中。 在上述這些根集對象中,最復雜和最難遍歷和標志的就是位于Dalvik虛擬機線程棧中的對象,因此接下
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 施工現場施工防生物安全事故制度
- 小學生心理健康教育的校本課程設計研究
- DB4404T 72-2024電梯維修保養(yǎng)服務安全規(guī)范
- 不服合作合同爭議仲裁起訴狀范本
- 個人股權轉讓合作合同模板
- 兩人合伙創(chuàng)業(yè)合同范本
- 個人股權轉讓合同簡單范文
- 二手房買賣合同簡易版
- 個人公寓租賃合同范本
- 產學研一體化碩士專班合作協(xié)議合同
- 行業(yè)會計比較(第三版)PPT完整全套教學課件
- 值機業(yè)務與行李運輸實務(第3版)高職PPT完整全套教學課件
- 高考英語語法填空專項訓練(含解析)
- 42式太極劍劍譜及動作說明(吳阿敏)
- 部編版語文小學五年級下冊第一單元集體備課(教材解讀)
- GB/T 10095.1-2022圓柱齒輪ISO齒面公差分級制第1部分:齒面偏差的定義和允許值
- 仁愛英語九年級下冊單詞表(中英文)
- 危險化學品企業(yè)安全生產標準化課件
- 巨鹿二中骨干教師個人工作業(yè)績材料
- 《美的歷程》導讀課件
- 心電圖 (史上最完美)課件
評論
0/150
提交評論