ART運行時與Dalvik虛擬機一樣,都使用了Mark-Sweep算法進行垃圾回收,因此它們的垃圾回收流程在總體上是一致的。但是ART運行時對堆的劃分更加細緻,因而在此基礎上實現了更多樣的回收策略。不同的策略有不同的回收力度,力度越大的回收策略,每次回收的內存就越多,並且它們都有各自的使用情景。這樣就可以使得每次執行GC時,可以最大限度地減少應用程序停頓。本文就詳細分析ART運行時的垃圾收集過程。
ART運行時的垃圾收集收集過程如圖1所示:
圖1 ART運行時的GC執行流程
圖1的最上面三個箭頭描述觸發GC的三種情況,左邊的流程圖描述非並行GC的執行過程,右邊的流程圖描述並行GC的執行流程,接下來我們就詳細圖中涉及到的所有細節。
在前面ART運行時爲新創建對象分配內存的過程分析一文中,我們提到了兩種可能會觸發GC的情況。第一種情況是沒有足夠內存分配請求的分存時,會調用Heap類的成員函數CollectGarbageInternal觸發一個原因爲kGcCauseForAlloc的GC。第二種情況下分配出請求的內存之後,堆剩下的內存超過一定的閥值,就會調用Heap類的成員函數RequestConcurrentGC請求執行一個並行GC。
Heap類的成員函數RequestConcurrentGC的實現如下所示:
- void Heap::RequestConcurrentGC(Thread* self) {
- // Make sure that we can do a concurrent GC.
- Runtime* runtime = Runtime::Current();
- DCHECK(concurrent_gc_);
- if (runtime == NULL || !runtime->IsFinishedStarting() ||
- !runtime->IsConcurrentGcEnabled()) {
- return;
- }
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- if (runtime->IsShuttingDown()) {
- return;
- }
- }
- if (self->IsHandlingStackOverflow()) {
- return;
- }
-
- // We already have a request pending, no reason to start more until we update
- // concurrent_start_bytes_.
- concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
-
- JNIEnv* env = self->GetJniEnv();
- DCHECK(WellKnownClasses::java_lang_Daemons != NULL);
- DCHECK(WellKnownClasses::java_lang_Daemons_requestGC != NULL);
- env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
- WellKnownClasses::java_lang_Daemons_requestGC);
- CHECK(!env->ExceptionCheck());
- }
這個函數定義在文件art/runtime/gc/heap.cc。
只有滿足以下四個條件,Heap類的成員函數RequestConcurrentGC纔會觸發一個並行GC:
1. ART運行時已經啓動完畢。
2. ART運行時支持並行GC。ART運行時默認是支持並行GC的,但是可以通過啓動選項-Xgc來關閉。
3. ART運行時不是正在關閉。
4. 當前線程沒有發生棧溢出。
上述4個條件都滿足之後,Heap類的成員函數RequestConcurrentGC就將成員變量concurrent_start_bytes_的值設置爲類型size_t的最大值,表示目前正有一個並行GC在等待執行,以阻止觸發另外一個並行GC。
最後,Heap類的成員函數RequestConcurrentGC調用Java層的java.lang.Daemons類的靜態成員函數requestGC請求執行一次並行GC。Java層的java.lang.Daemons類在加載的時候,會啓動五個與堆或者GC相關的守護線程,如下所示:
- public final class Daemons {
- ......
-
- public static void start() {
- ReferenceQueueDaemon.INSTANCE.start();
- FinalizerDaemon.INSTANCE.start();
- FinalizerWatchdogDaemon.INSTANCE.start();
- HeapTrimmerDaemon.INSTANCE.start();
- GCDaemon.INSTANCE.start();
- }
-
- ......
- }
這個類定義在文件libcore/libart/src/main/java/java/lang/Daemons.java中。
這五個守護線程分別是:
1. ReferenceQueueDaemon:引用隊列守護線程。我們知道,在創建引用對象的時候,可以關聯一個隊列。當被引用對象引用的對象被GC回收的時候,被引用對象就會被加入到其創建時關聯的隊列去。這個加入隊列的操作就是由ReferenceQueueDaemon守護線程來完成的。這樣應用程序就可以知道那些被引用對象引用的對象已經被回收了。
2. FinalizerDaemon:析構守護線程。對於重寫了成員函數finalize的對象,它們被GC決定回收時,並沒有馬上被回收,而是被放入到一個隊列中,等待FinalizerDaemon守護線程去調用它們的成員函數finalize,然後再被回收。
3. FinalizerWatchdogDaemon:析構監護守護線程。用來監控FinalizerDaemon線程的執行。一旦檢測那些重定了成員函數finalize的對象在執行成員函數finalize時超出一定的時候,那麼就會退出VM。
4. HeapTrimmerDaemon:堆裁剪守護線程。用來執行裁剪堆的操作,也就是用來將那些空閒的堆內存歸還給系統。
5. GCDaemon:並行GC線程。用來執行並行GC。
Java層的java.lang.Daemons類的靜態成員函數requestGC被調用時,就會喚醒上述的並行GC線程,然後這個並行GC線程就會通過JNI調用Heap類的成員函數ConcurrentGC,它的實現如下所示:
- void Heap::ConcurrentGC(Thread* self) {
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- if (Runtime::Current()->IsShuttingDown()) {
- return;
- }
- }
-
- // Wait for any GCs currently running to finish.
- if (WaitForConcurrentGcToComplete(self) == collector::kGcTypeNone) {
- CollectGarbageInternal(next_gc_type_, kGcCauseBackground, false);
- }
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
只要ART運行時當前不是處於正在關閉的狀態,那麼Heap類的成員函數ConcurrentGC就會檢查當前是否正在執行GC。如果是的話,那麼就等待它執行完成,然後再調用Heap類的成員函數CollectGarbageInternal觸發一個原因爲kGcCauseBackground的GC。否則的話,就直接調用Heap類的成員函數CollectGarbageInternal觸發一個原因爲kGcCauseBackground的GC。
從這裏就可以看到,無論是觸發GC的原因是kGcCauseForAlloc,還是kGcCauseBackground,最終都是通過調用Heap類的成員函數CollectGarbageInternal來執行GC的。此外,還有第三種情況會觸發GC,如下所示:
- void Heap::CollectGarbage(bool clear_soft_references) {
- // Even if we waited for a GC we still need to do another GC since weaks allocated during the
- // last GC will not have necessarily been cleared.
- Thread* self = Thread::Current();
- WaitForConcurrentGcToComplete(self);
- CollectGarbageInternal(collector::kGcTypeFull, kGcCauseExplicit, clear_soft_references);
- }
這個函數定義在文件art/runtime/gc/heap.cc。
當我們調用Java層的java.lang.System的靜態成員函數gc時,如果ART運行時支持顯式GC,那麼就它就會通過JNI調用Heap類的成員函數CollectGarbageInternal來觸發一個原因爲kGcCauseExplicit的GC。ART運行時默認是支持顯式GC的,但是可以通過啓動選項-XX:+DisableExplicitGC來關閉。
從上面的分析就可以看出,ART運行時在三種情況下會觸發GC,這三種情況通過三個枚舉kGcCauseForAlloc、kGcCauseBackground和kGcCauseExplicitk來描述。這三人枚舉的定義如下所示:
- // What caused the GC?
- enum GcCause {
- // GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
- // retrying allocation.
- kGcCauseForAlloc,
- // A background GC trying to ensure there is free memory ahead of allocations.
- kGcCauseBackground,
- // An explicit System.gc() call.
- kGcCauseExplicit,
- };
這三個枚舉定義在文件art/runtime/gc/heap.h中。
從上面的分析還可以看出,ART運行時的所有GC都是以Heap類的成員函數CollectGarbageInternal爲入口,它的實現如下所示:
- collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, GcCause gc_cause,
- bool clear_soft_references) {
- Thread* self = Thread::Current();
- ......
-
- // Ensure there is only one GC at a time.
- bool start_collect = false;
- while (!start_collect) {
- {
- MutexLock mu(self, *gc_complete_lock_);
- if (!is_gc_running_) {
- is_gc_running_ = true;
- start_collect = true;
- }
- }
- if (!start_collect) {
- // TODO: timinglog this.
- WaitForConcurrentGcToComplete(self);
- ......
- }
- }
-
- ......
-
- if (gc_type == collector::kGcTypeSticky &&
- alloc_space_->Size() < min_alloc_space_size_for_sticky_gc_) {
- gc_type = collector::kGcTypePartial;
- }
-
- ......
-
- collector::MarkSweep* collector = NULL;
- for (const auto& cur_collector : mark_sweep_collectors_) {
- if (cur_collector->IsConcurrent() == concurrent_gc_ && cur_collector->GetGcType() == gc_type) {
- collector = cur_collector;
- break;
- }
- }
- ......
-
- collector->clear_soft_references_ = clear_soft_references;
- collector->Run();
- ......
-
- {
- MutexLock mu(self, *gc_complete_lock_);
- is_gc_running_ = false;
- last_gc_type_ = gc_type;
- // Wake anyone who may have been waiting for the GC to complete.
- gc_complete_cond_->Broadcast(self);
- }
-
- ......
-
- return gc_type;
- }
這個函數定義在文件art/runtime/gc/heap.cc。
參數gc_type和gc_cause分別用來描述要執行的GC的類型和原因,而參數clear_soft_references用來描述是否要回收被軟引用對象引用的對象。
Heap類的成員函數CollectGarbageInternal的執行邏輯如下所示:
1. 通過一個while循環不斷地檢查Heap類的成員變量is_gc_running_,直到它的值等於false爲止,這表示當前沒有其它線程正在執行GC。當它的值等於true時,就表示在其它線程正在執行GC,這時候就要調用Heap類的成員函數WaitForConcurrentGcToComplete等待其執行完成。注意,在當前GC執行之前,Heap類的成員變量is_gc_running_會被設置爲true。
2. 如果當前請求執行的GC的類型爲kGcTypeSticky,但是當前Allocation Space的大小小於Heap類的成員變量min_alloc_space_size_for_sticky_gc_指定的閥值,那麼就改爲執行類型爲kGcTypePartial。關於類型爲kGcTypeSticky的GC的執行限制,可以參數前面ART運行時爲新創建對象分配內存的過程分析一文。
3. 從Heap類的成員變量mark_sweep_collectors_指向的一個垃圾收集器列表找到一個合適的垃圾收集器來執行GC。從前面ART運行時Java堆創建過程分析一文可以知道,ART運行時在內部創建了六個垃圾收集器。這六個垃圾收集器分爲兩組,一組支持並行GC,另一組不支持。每一組都是由三個類型分別爲kGcTypeSticky、kGcTypePartial和kGcTypeFull的垃垃圾收集器組成。這裏說的合適的垃圾收集器,是指並行性與Heap類的成員變量concurrent_gc_一致,並且類型也與參數gc_type一致的垃圾收集器。
4. 找到合適的垃圾收集器之後,就將參數clear_soft_references的值保存它的成員變量clear_soft_references_中,以便可以告訴它要不要回收被軟引用對象引用的對象,然後再調用它的成員函數Run來執行GC。
5. GC執行完畢,將Heap類的成員變量is_gc_running_設置爲false,以表示當前GC已經執行完畢,下一次請求的GC可以執行了。此外,也會將Heap類的成員變量last_gc_type_設置爲當前執行的GC的類型。這樣下一次執行GC時,就可以執行另外一個不同類型的GC。例如,如果上一次執行的GC的類型爲kGcTypeSticky,那麼接下來的兩次GC的類型就可以設置爲kGcTypePartial和kGcTypeFull,這樣可以使得每次都能執行有效的GC。
6. 通過Heap類的成員變量gc_complete_cond_喚醒那些正在等待GC執行完成的線程。
在上面的六個步驟中,最重要的就是第四步了。從前面ART運行時垃圾收集機制簡要介紹和學習計劃一文可以知道,所有的垃圾收集器都是從GarbageCollector類繼承下來的,因此上面的第四步實際上執行的是GarbageCollector類的成員函數Run,它的實現如下所示:
- void GarbageCollector::Run() {
- ThreadList* thread_list = Runtime::Current()->GetThreadList();
- uint64_t start_time = NanoTime();
- pause_times_.clear();
- duration_ns_ = 0;
-
- InitializePhase();
-
- if (!IsConcurrent()) {
- // Pause is the entire length of the GC.
- uint64_t pause_start = NanoTime();
- ATRACE_BEGIN("Application threads suspended");
- thread_list->SuspendAll();
- MarkingPhase();
- ReclaimPhase();
- thread_list->ResumeAll();
- ATRACE_END();
- uint64_t pause_end = NanoTime();
- pause_times_.push_back(pause_end - pause_start);
- } else {
- Thread* self = Thread::Current();
- {
- ReaderMutexLock mu(self, *Locks::mutator_lock_);
- MarkingPhase();
- }
- bool done = false;
- while (!done) {
- uint64_t pause_start = NanoTime();
- ATRACE_BEGIN("Suspending mutator threads");
- thread_list->SuspendAll();
- ATRACE_END();
- ATRACE_BEGIN("All mutator threads suspended");
- done = HandleDirtyObjectsPhase();
- ATRACE_END();
- uint64_t pause_end = NanoTime();
- ATRACE_BEGIN("Resuming mutator threads");
- thread_list->ResumeAll();
- ATRACE_END();
- pause_times_.push_back(pause_end - pause_start);
- }
- {
- ReaderMutexLock mu(self, *Locks::mutator_lock_);
- ReclaimPhase();
- }
- }
-
- uint64_t end_time = NanoTime();
- duration_ns_ = end_time - start_time;
-
- FinishPhase();
- }
這個函數定義在文件art/runtime/gc/collector/garbage_collector.cc中。
GarbageCollector類的成員函數Run的實現就對應於圖1所示的左邊和右邊的兩個流程。
圖1所示的左邊流程是用來執行非並行GC的,過程如下所示:
1. 調用子類實現的成員函數InitializePhase執行GC初始化階段。
2. 掛起所有的ART運行時線程。
3. 調用子類實現的成員函數MarkingPhase執行GC標記階段。
4. 調用子類實現的成員函數ReclaimPhase執行GC回收階段。
5. 恢復第2步掛起的ART運行時線程。
6. 調用子類實現的成員函數FinishPhase執行GC結束階段。
圖1所示的右邊流程是用來執行並行GC的,過程如下所示:
1. 調用子類實現的成員函數InitializePhase執行GC初始化階段。
2. 獲取用於訪問Java堆的鎖。
3. 調用子類實現的成員函數MarkingPhase執行GC並行標記階段。
4. 釋放用於訪問Java堆的鎖。
5. 掛起所有的ART運行時線程。
6. 調用子類實現的成員函數HandleDirtyObjectsPhase處理在GC並行標記階段被修改的對象。。
7. 恢復第4步掛起的ART運行時線程。
8. 重複第5到第7步,直到所有在GC並行階段被修改的對象都處理完成。
9. 獲取用於訪問Java堆的鎖。
10. 調用子類實現的成員函數ReclaimPhase執行GC回收階段。
11. 釋放用於訪問Java堆的鎖。
12. 調用子類實現的成員函數FinishPhase執行GC結束階段。
從上面的分析就可以看出,並行GC和非並行GC的區別在於:
1. 非並行GC的標記階段和回收階段是在掛住所有的ART運行時線程的前提下進行的,因此,只需要執行一次標記即可。
2. 並行GC的標記階段只鎖住了Java堆,因此它不能阻止那些不是正在分配對象的ART運行時線程同時運行,而這些同進運行的ART運行時線程可能會引用了一些在之前的標記階段沒有被標記的對象。如果不對這些對象進行重新標記的話,那麼就會導致它們被GC回收,造成錯誤。因此,與非並行GC相比,並行GC多了一個處理髒對象的階段。所謂的髒對象就是我們前面說的在GC標記階段同時運行的ART運行時線程訪問或者修改過的對象。
3. 並行GC並不是自始至終都是並行的,例如,處理髒對象的階段就是需要掛起除GC線程以外的其它ART運行時線程,這樣纔可以保證標記階段可以結束。
從前面ART運行時垃圾收集機制簡要介紹和學習計劃一文可以知道,GarbageCollector類有三個直接或者間接的子類MarkSweep、PartialMarkSweep和StickyMarkSweep都可以用來執行垃圾回收,其中,PartialMarkSweep類又是從MarkSweep類直接繼承下來的,而StickyMarkSweep類是從PartialMarkSweep類直接繼承下來的。MarkSweep類用來回收Zygote Space和Allocation Space的垃圾,PartialMarkSweep類用來回收Allocation Space的垃圾,StickyMarkSweep類用來回收上次GC以來在Allcation Space上分配的最終又沒有被引用的垃圾。
接下來,我們就主要分析ART運行時線程的掛起和恢復過程,以及MarkSweep、PartialMarkSweep和StickyMarkSweep這三個類是執行InitializePhase、MarkingPhase、HandleDirtyObjectsPhase、ReclaimPhase和FinishPhase的五個GC階段的過程。
1. ART運行時線程的掛起
從上面的分析可以知道,ART運行時線程的掛起是通過調用ThreadList類的成員函數SuspendAll實現的,如下所示:
- void ThreadList::SuspendAll() {
- Thread* self = Thread::Current();
- ......
-
- {
- MutexLock mu(self, *Locks::thread_list_lock_);
- {
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- // Update global suspend all state for attaching threads.
- ++suspend_all_count_;
- // Increment everybody's suspend count (except our own).
- for (const auto& thread : list_) {
- if (thread == self) {
- continue;
- }
- ......
- thread->ModifySuspendCount(self, +1, false);
- }
- }
- }
-
- // Block on the mutator lock until all Runnable threads release their share of access.
- #if HAVE_TIMED_RWLOCK
- // Timeout if we wait more than 30 seconds.
- if (UNLIKELY(!Locks::mutator_lock_->ExclusiveLockWithTimeout(self, 30 * 1000, 0))) {
- UnsafeLogFatalForThreadSuspendAllTimeout(self);
- }
- #else
- Locks::mutator_lock_->ExclusiveLock(self);
- #endif
-
- ......
- }
這個函數定義在文件art/runtime/thread_list.cc中。
所有的ART運行時線程都保存在ThreadList類的成員變量list_描述的一個列表,遍歷這個列表時,需要獲取Lock類的成員變量thread_list_lock_描述的一個互斥鎖。
ThreadList類有一個成員變量suspend_all_count_,用來描述全局的線程掛起計數器。在所有的ART運行時線程掛起期間,如果有新的線程將自己註冊爲ART運行時線程,那麼它也會將自己掛起來,而判斷所有的ART運行時線程是不是處於掛起期間,就是通過ThreadList類的成員變量suspend_all_count_的值是否大於0進行的。因此,ThreadList類的成員函數SuspendAll在掛起所有的ART運行時線程之前,會將ThreadList類的成員變量suspend_all_count_的值增加1。
接下來,ThreadList類的成員函數SuspendAll遍歷所有的ART運行時線程,並且調用Thread類的成員函數ModifySuspendCount將它內部的線程計算數器增加1,如下所示:
- void Thread::AtomicSetFlag(ThreadFlag flag) {
- android_atomic_or(flag, &state_and_flags_.as_int);
- }
-
- void Thread::AtomicClearFlag(ThreadFlag flag) {
- android_atomic_and(-1 ^ flag, &state_and_flags_.as_int);
- }
-
- ......
-
- void Thread::ModifySuspendCount(Thread* self, int delta, bool for_debugger) {
- ......
-
- suspend_count_ += delta;
- ......
-
- if (suspend_count_ == 0) {
- AtomicClearFlag(kSuspendRequest);
- } else {
- AtomicSetFlag(kSuspendRequest);
- }
- }
這三個函數定義在文件art/runtime/thread.cc中。
Thread類的成員函數ModifySuspendCount的實現很簡單,它主要就是將成員變量suspend_count_的值增加delta,並且判斷增加後的值是否等於0。如果等於0,就調用成員函數AtomicClearFlag將另外一個成員變量state_and_flags_的int值的kSuspendRequest位清0,表示線程沒有掛起請求。否則的話,就調用成員函數AtomicSetFlag將成員變量state_and_flags_的int值的kSuspendRequest位置1,表示線程有掛起請求。
回到前面ThreadList類的成員函數SuspendAll中,全局ART運行時線程掛起計數器和每一個ART運行時線程內部的線程掛起計數器的操作都是需要在獲取Locks類的靜態成員變量thread_suspend_count_lock_描述的一個互斥鎖的前提下進行的。
最後,ThreadList類的成員函數SuspendAll通過獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的寫訪問來等待所有的ART運行時線程掛起的。這是如何做到的呢?在前面Android運行時ART執行類方法的過程分析一文中,我們提到,ART運行時提供給由DEX字節碼翻譯而來的本地機器代碼使用的一個函數表中,包含了一個pCheckSuspend函數指針,該函數指針指向了函數CheckSuspendFromCode。於是,每一個ART運行時線程在執行本地機器代碼的過程中,就會週期性地通過調用函數CheckSuspendFromCode來檢查自己是否需要掛起。這一點與前面Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機線程掛起的過程是類似的。
函數CheckSuspendFromCode的實現如下所示:
- void CheckSuspendFromCode(Thread* thread)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- ......
- CheckSuspend(thread);
- }
這個函數定義在文件art/runtime/entrypoints/quick/quick_thread_entrypoints.cc中。
函數CheckSuspendFromCode調用另外一個函數CheckSuspend檢查當前線程是否需要掛起,後者的實現如下所示:
- static inline void CheckSuspend(Thread* thread) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- for (;;) {
- if (thread->ReadFlag(kCheckpointRequest)) {
- thread->RunCheckpointFunction();
- thread->AtomicClearFlag(kCheckpointRequest);
- } else if (thread->ReadFlag(kSuspendRequest)) {
- thread->FullSuspendCheck();
- } else {
- break;
- }
- }
- }
這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。
從上面的分析可以知道,如果當前線程的線程掛起計數器不等於0,那麼它內部的一個標記位kSuspendRequest被設置爲1。這時候函數CheckSuspend就會調用Thread類的成員函數FullSuspendCheck來將自己掛起。此外,函數CheckSuspend還會檢查線程內部的另外一個標記位kCheckpointRequest是否被設置爲1。如果被設置爲1的話,那麼就說明線程有一個Check Point需要執行,這時候就會先調用Thread類的成員函數RunCheckpointFunction運行該Check Point,接着再將線程內部的標記位kCheckpointRequest清0。關於線程的Check Point,我們後面再分析。
Thread類的成員函數FullSuspendCheck的實現如下所示:
- void Thread::FullSuspendCheck() {
- ......
- // Make thread appear suspended to other threads, release mutator_lock_.
- TransitionFromRunnableToSuspended(kSuspended);
- // Transition back to runnable noting requests to suspend, re-acquire share on mutator_lock_.
- TransitionFromSuspendedToRunnable();
- ......
- }
這個函數定義在文件art/runtime/thread.cc中。
Thread類的成員函數FullSuspendCheck首先是調用成員函數TransitionFromRunnableToSuspended將自己從運行狀態修改爲掛起狀態,接着再調用成員函數TransitionFromSuspendedToRunnable將自己從掛起狀態修改爲運行狀態。通過Thread類的這兩個神奇的成員函數,就實現了將當前線程掛起的功能。接下來我們就繼續分析它們是怎麼實現的。
Thread類的成員函數TransitionFromRunnableToSuspended的實現如下所示:
- inline void Thread::TransitionFromRunnableToSuspended(ThreadState new_state) {
- ......
- union StateAndFlags old_state_and_flags;
- union StateAndFlags new_state_and_flags;
- do {
- old_state_and_flags = state_and_flags_;
- // Copy over flags and try to clear the checkpoint bit if it is set.
- new_state_and_flags.as_struct.flags = old_state_and_flags.as_struct.flags & ~kCheckpointRequest;
- new_state_and_flags.as_struct.state = new_state;
- // CAS the value without a memory barrier, that will occur in the unlock below.
- } while (UNLIKELY(android_atomic_cas(old_state_and_flags.as_int, new_state_and_flags.as_int,
- &state_and_flags_.as_int) != 0));
- // If we toggled the checkpoint flag we must have cleared it.
- uint16_t flag_change = new_state_and_flags.as_struct.flags ^ old_state_and_flags.as_struct.flags;
- if (UNLIKELY((flag_change & kCheckpointRequest) != 0)) {
- RunCheckpointFunction();
- }
- // Release share on mutator_lock_.
- Locks::mutator_lock_->SharedUnlock(this);
- }
這個函數定義在文件art/runtime/thread-inl.h中。
線程的狀態和標記位均保存在Thread類的成員變量state_and_flags_描述的一個Union結構體中。Thread類的成員函數TransitionFromRunnableToSuspended首先是將線程舊的狀態和標誌位保存起來,然後再清空它的kCheckpointRequest標誌位,以及將它的狀態設置爲參數new_state描述的狀態,即kSuspended狀態。
設置好線程新的標記位和狀態之後,Thread類的成員函數TransitionFromRunnableToSuspended再檢查線程原來的標記位kCheckpointRequest是否等於1。如果等於1的話,那麼在釋放Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的讀訪問之前,先調用Thread類的成員函數RunCheckpointFunction來執行線程的Check Point。
Thread類的成員函數TransitionFromSuspendedToRunnable的實現如下所示:
- inline ThreadState Thread::TransitionFromSuspendedToRunnable() {
- bool done = false;
- union StateAndFlags old_state_and_flags = state_and_flags_;
- int16_t old_state = old_state_and_flags.as_struct.state;
- ......
- do {
- ......
- old_state_and_flags = state_and_flags_;
- ......
- if (UNLIKELY((old_state_and_flags.as_struct.flags & kSuspendRequest) != 0)) {
- // Wait while our suspend count is non-zero.
- MutexLock mu(this, *Locks::thread_suspend_count_lock_);
- old_state_and_flags = state_and_flags_;
- ......
- while ((old_state_and_flags.as_struct.flags & kSuspendRequest) != 0) {
- // Re-check when Thread::resume_cond_ is notified.
- Thread::resume_cond_->Wait(this);
- old_state_and_flags = state_and_flags_;
- ......
- }
- ......
- }
- // Re-acquire shared mutator_lock_ access.
- Locks::mutator_lock_->SharedLock(this);
- // Atomically change from suspended to runnable if no suspend request pending.
- old_state_and_flags = state_and_flags_;
- ......
- if (LIKELY((old_state_and_flags.as_struct.flags & kSuspendRequest) == 0)) {
- union StateAndFlags new_state_and_flags = old_state_and_flags;
- new_state_and_flags.as_struct.state = kRunnable;
- // CAS the value without a memory barrier, that occurred in the lock above.
- done = android_atomic_cas(old_state_and_flags.as_int, new_state_and_flags.as_int,
- &state_and_flags_.as_int) == 0;
- }
- if (UNLIKELY(!done)) {
- // Failed to transition to Runnable. Release shared mutator_lock_ access and try again.
- Locks::mutator_lock_->SharedUnlock(this);
- }
- } while (UNLIKELY(!done));
- return static_cast<ThreadState>(old_state);
- }
這個函數定義在文件art/runtime/thread-inl.h中。
Thread類的成員函數TransitionFromSuspendedToRunnable的實現很簡單,它通過一個do...while循環不斷地判斷自己是否有一個掛起請求。如果有的話,就在Thread類的靜態成員變量resume_cond_描述的一個條件變量上進行等待,直到被其它線程喚醒爲止。被喚醒之後,又在獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的讀訪問的前提下,將自己的狀態設置爲運行狀態,即kRunnable。如果設置成功,就結束執行do...while循環。否則的話,就說明又可能有其它線程請求將自己掛起,因此Thread類的成員函數TransitionFromSuspendedToRunnable又需要釋放之前獲得的讀寫鎖的讀訪問,然後再重新執行一遍do...while循環。
Thread類的成員函數TransitionFromRunnableToSuspended和TransitionFromSuspendedToRunnable均是在獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的讀訪問的前提下執行的。假設執行GC的線程在調用ThreadList類的成員函數SuspendAll獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的寫訪問之前,當前線程還沒有獲得該讀寫鎖的讀訪問,那麼很明顯當它要獲得讀訪問時,就會進入等待狀態,因爲讀寫鎖的讀訪問和寫訪問是互斥的。
另一方面,如果執行GC的線程在調用ThreadList類的成員函數SuspendAll獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的寫訪問之前,當前線程已經獲得了該讀寫鎖的讀訪問,那麼隨後它就會調用Thread類的成員函數TransitionFromRunnableToSuspended釋放對該讀寫鎖的讀訪問,因此GC線程就可以獲得該讀寫鎖的寫訪問。等到當前線程再調用Thread類的成員函數TransitionFromSuspendedToRunnable獲得該讀寫鎖的讀訪問時,就會進入等待狀態了。
2. ART運行時線程的恢復
理解了ART運行時線程的掛起過程之後,再理解它們的恢復狀態就容易多了,這是通過調用ThreadList類的成員函數ResumeAll實現的,如下所示:
- void ThreadList::ResumeAll() {
- Thread* self = Thread::Current();
- ......
-
- Locks::mutator_lock_->ExclusiveUnlock(self);
- {
- MutexLock mu(self, *Locks::thread_list_lock_);
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- // Update global suspend all state for attaching threads.
- --suspend_all_count_;
- // Decrement the suspend counts for all threads.
- for (const auto& thread : list_) {
- if (thread == self) {
- continue;
- }
- thread->ModifySuspendCount(self, -1, false);
- }
-
- // Broadcast a notification to all suspended threads, some or all of
- // which may choose to wake up. No need to wait for them.
- ......
- Thread::resume_cond_->Broadcast(self);
- }
- ......
- }
這個函數定義在文件art/runtime/thread_list.cc中。
ThreadList類的成員函數ResumeAll所做的事情剛好與成員函數SuspendAll相反,它首先是釋放Locks類的靜態成員函數mutator_lock_描述的讀寫鎖的寫訪問,接着再減少ART運行時線程全局掛起計數器以及每一個ART運行時線程內部的掛起計數器,最再喚醒等待在Thread類的靜態成員變量resume_cond_描述的一個條件變量上的其它ART運行時線程。
3. InitializePhase
MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的初始化階段都是相同的,都是由MarkSweep類的成員函數InitializePhase來實現,如下所示:
- void MarkSweep::InitializePhase() {
- timings_.Reset();
- base::TimingLogger::ScopedSplit split("InitializePhase", &timings_);
- mark_stack_ = heap_->mark_stack_.get();
- DCHECK(mark_stack_ != nullptr);
- SetImmuneRange(nullptr, nullptr);
- soft_reference_list_ = nullptr;
- weak_reference_list_ = nullptr;
- finalizer_reference_list_ = nullptr;
- phantom_reference_list_ = nullptr;
- cleared_reference_list_ = nullptr;
- freed_bytes_ = 0;
- freed_large_object_bytes_ = 0;
- freed_objects_ = 0;
- freed_large_objects_ = 0;
- class_count_ = 0;
- array_count_ = 0;
- other_count_ = 0;
- large_object_test_ = 0;
- large_object_mark_ = 0;
- classes_marked_ = 0;
- overhead_time_ = 0;
- work_chunks_created_ = 0;
- work_chunks_deleted_ = 0;
- reference_count_ = 0;
- java_lang_Class_ = Class::GetJavaLangClass();
- CHECK(java_lang_Class_ != nullptr);
-
- FindDefaultMarkBitmap();
-
- // Do any pre GC verification.
- timings_.NewSplit("PreGcVerification");
- heap_->PreGcVerification(this);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數InitializePhase主要就是重置一下接下來GC要用到的一些成員變量,其中,比較重要的就是調用另外一個成員函數FindDefaultMarkBitmap獲取一個Default Mark Bitmap,它的實現如下所示:
- void MarkSweep::FindDefaultMarkBitmap() {
- base::TimingLogger::ScopedSplit split("FindDefaultMarkBitmap", &timings_);
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyAlwaysCollect) {
- current_mark_bitmap_ = space->GetMarkBitmap();
- CHECK(current_mark_bitmap_ != NULL);
- return;
- }
- }
- ......
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
從這裏的就可以看出,Default Mark Bitmap就是回收策略爲kGcRetentionPolicyAlwaysCollect的Space對應的Mark Bitmap。從前面ART運行時Java堆創建過程分析一文可以知道,這個Space就是Allocation Space(如果Allocation Space還沒有從Zygote Space劃分出來,那就是Zygote Space)。
獲得的Default Mark Bitmap保存在MarkSweep類的成員變量current_mark_bitmap_中。
4. MarkingPhase
MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的標記階段都是以MarkSweep類的成員函數MarkingPhase爲入口,它的實現如下所示:
- void MarkSweep::MarkingPhase() {
- base::TimingLogger::ScopedSplit split("MarkingPhase", &timings_);
- Thread* self = Thread::Current();
-
- BindBitmaps();
- FindDefaultMarkBitmap();
-
- // Process dirty cards and add dirty cards to mod union tables.
- heap_->ProcessCards(timings_);
-
- // Need to do this before the checkpoint since we don't want any threads to add references to
- // the live stack during the recursive mark.
- timings_.NewSplit("SwapStacks");
- heap_->SwapStacks();
-
- WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
- if (Locks::mutator_lock_->IsExclusiveHeld(self)) {
- // If we exclusively hold the mutator lock, all threads must be suspended.
- MarkRoots();
- } else {
- MarkThreadRoots(self);
- // At this point the live stack should no longer have any mutators which push into it.
- MarkNonThreadRoots();
- }
- live_stack_freeze_size_ = heap_->GetLiveStack()->Size();
- MarkConcurrentRoots();
-
- heap_->UpdateAndMarkModUnion(this, timings_, GetGcType());
- MarkReachableObjects();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkingPhase標記對象的過程如下所示:
A. 調用成員函數BindBitmap設置回收範圍。
B. 調用成員函數FindDefaultMarkBitmap找到回收策略爲kGcRetentionPolicyAlwaysCollect的Space對應的Mark Bitmap,並且保存在成員變量current_mark_bitmap_中。這個函數我們在前面已經分析過了。
C. 調用Heap類的成員函數ProcessCards處理Card Table中的Dirty Card,以及這些Dirty Card添加到對應的Mod Union Table中去。
D. 調用Heap類的成員函數SwapStacks交換ART運行時的Allocation Stack和Live Stack。
E. 對於非並行GC,當前線程在掛起其它ART運行時線程的過程中,已經獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的寫訪問,因此這時候就調用成員函數MarkRoots來標記那些不可以在沒有獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的情況下訪問的根集對象。注意,MarkSweep類的成員函數MarkRoots只通過當前線程來標記根集對象。
F. 對於並行GC,由於標記階段並沒有掛起其它的ART運行時線程,因此這時候就調用成員函數MarkThreadRoots來併發標記那些不可以在沒有獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的情況下訪問的位於線程調用棧中的根集對象,接着再在當前線程中調用成員函數MarkNonThreadRoots標記那些不可以在沒有獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的情況下訪問的其它根集對象。
G. 獲得Live Stack的大小,保存成員變量live_stack_freeze_size_中。注意,這時候Live Stack的大小即爲交換Allocation Stack和Live Stack之前Allocation Stack的大小,即從上次GC以來新分配的對象的個數。
H. 調用成員函數MarkConcurrentRoots標記那些可以在沒有獲得Locks類的靜態成員變量mutator_lock_描述的讀寫鎖的情況下訪問的根集對象。
I. 調用Heap類的成員函數UpdateAndMarkModUnion處理Mod Union Table中的Dirty Card。
J. 調用成員函數MarkReachableObjects遞歸標記那些可以從根集對象到達的其它對象。
上述就是GC的標記階段,它是一個很複雜的過程,涉及到的關鍵函數有MarkSweep類的成員函數BindBitmap、MarkRoots、MarkThreadRoots、MarkNonThreadRoots、MarkConcurrentRoots和MarkReachableObjects,以及Heap類的成員函數ProcessCards、SwapStacks和UpdateAndMarkModUnion。接下來我們就詳細分析這些函數的實現,以及這些函數涉及到的概念。
在前面ART運行時垃圾收集機制簡要介紹和學習計劃一文中,我們提到,Mark Sweep、Partial Mark Sweep和Sticky Mark Sweep的回收範圍是不同的,它們分別通過實現的自己的成員函數BindBitmap來限定回收範圍,因此,接下來我們就分別分析MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的成員函數BindBitmap的實現。
MarkSweep類的成員函數BindBitmap的實現如下所示:
- void MarkSweep::BindBitmaps() {
- timings_.StartSplit("BindBitmaps");
- WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
- // Mark all of the spaces we never collect as immune.
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyNeverCollect) {
- ImmuneSpace(space);
- }
- }
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數BindBitmap調用另外一個成員函數ImmuneSpace將回收策略爲kGcRetentionPolicyNeverCollect的Space的Live Bitmap和Mark Bitmap進行交換,這樣做的效果就相當於不對該Space進行GC。從前面ART運行時Java堆創建過程分析一文可以知道,回收策略爲kGcRetentionPolicyNeverCollect的Space就是Image Space,因此,MarkSweep類就只對Zygote Space和Allocation Space進行GC。
MarkSweep類的成員函數ImmuneSpace的實現如下所示:
- void MarkSweep::ImmuneSpace(space::ContinuousSpace* space) {
- // Bind live to mark bitmap if necessary.
- if (space->GetLiveBitmap() != space->GetMarkBitmap()) {
- BindLiveToMarkBitmap(space);
- }
-
- // Add the space to the immune region.
- if (immune_begin_ == NULL) {
- DCHECK(immune_end_ == NULL);
- SetImmuneRange(reinterpret_cast<Object*>(space->Begin()),
- reinterpret_cast<Object*>(space->End()));
- } else {
- const space::ContinuousSpace* prev_space = nullptr;
- // Find out if the previous space is immune.
- for (space::ContinuousSpace* cur_space : GetHeap()->GetContinuousSpaces()) {
- if (cur_space == space) {
- break;
- }
- prev_space = cur_space;
- }
- // If previous space was immune, then extend the immune region. Relies on continuous spaces
- // being sorted by Heap::AddContinuousSpace.
- if (prev_space != NULL &&
- immune_begin_ <= reinterpret_cast<Object*>(prev_space->Begin()) &&
- immune_end_ >= reinterpret_cast<Object*>(prev_space->End())) {
- immune_begin_ = std::min(reinterpret_cast<Object*>(space->Begin()), immune_begin_);
- immune_end_ = std::max(reinterpret_cast<Object*>(space->End()), immune_end_);
- }
- }
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數ImmuneSpace首先是判斷參數space描述的Space的Live Bitmap和Mark Bitmap是否是同一個Bitmap。如果是的話,那麼就不需要調用成員函數BindLiveToMarkBitmap對它們進行交換了。一般來說,一個Space的Live Bitmap和Mark Bitmap指向的是不同的Bitmap的,但是Image Space比較特殊,它們指向的是同一個Bitmap。這一點在前面ART運行時Java堆創建過程分析一文中有提到。
如果之前有Space也被設置爲不進行回收,那麼除了調用成員函數BindLiveToMarkBitmap交換參數space描述的Space的Live Bitmap和Mark Bitmap之外,MarkSweep類的成員函數ImmuneSpace還會參數space描述的Space佔用的內存區域與之前也被設置爲不進行回收的Space佔用的內存區域進行合併,最後將得到的不會被回收的內存總區域的開始位置和結束位置記錄在MarkSweep類的成員變量immune_begin_ 和immune_end_中。
另一方面,如果之前沒有Space也被設置過不進行回收,那麼MarkSweep類的成員函數ImmuneSpace就會將參數space描述的Space佔用的內存區域的開始位置和結束位置記錄在MarkSweep類的成員變量immune_begin_ 和immune_end_中,以便 後面可以調用MarkSweep類的成員函數IsImmune判斷一個對象是否位於非回收Space中。
MarkSweep類的成員函數IsImmune的實現如下所示:
- class MarkSweep : public GarbageCollector {
- public:
- ......
-
- protected:
- ......
-
- // Returns true if an object is inside of the immune region (assumed to be marked).
- bool IsImmune(const mirror::Object* obj) const {
- return obj >= immune_begin_ && obj < immune_end_;
- }
-
- ......
- };
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數IsImmune只需要將參數obj描述的對象地址與MarkSweep類的成員變量immune_begin_和immune_end_進行比較,就可以知道該對象是否位於非回收Space中了。
PartialMarkSweep類重寫了父類MarkSweep的成員函數BindBitmap,它的實現如下所示:
- void PartialMarkSweep::BindBitmaps() {
- MarkSweep::BindBitmaps();
-
- WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
- // For partial GCs we need to bind the bitmap of the zygote space so that all objects in the
- // zygote space are viewed as marked.
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect) {
- CHECK(space->IsZygoteSpace());
- ImmuneSpace(space);
- }
- }
- }
這個函數定義在文件art/runtime/gc/collector/partial_mark_sweep.cc中。
PartialMarkSweep類的成員函數BindBitmap除了調用父類MarkSweep類的成員函數BindBitmap將Image Space標記爲不回收之外,還會將回收策略爲kGcRetentionPolicyFullCollect的Space標記爲不回收。從前面ART運行時Java堆創建過程分析一文可以知道,回收策略爲kGcRetentionPolicyFullCollect的Space就是Zygote Space,因此,MarkSweep類就只對Allocation Space進行GC。
StickyMarkSweep類重寫了父類PartialMarkSweep的成員函數BindBitmap,它的實現如下所示:
- void StickyMarkSweep::BindBitmaps() {
- PartialMarkSweep::BindBitmaps();
-
- WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
- // For sticky GC, we want to bind the bitmaps of all spaces as the allocation stack lets us
- // know what was allocated since the last GC. A side-effect of binding the allocation space mark
- // and live bitmap is that marking the objects will place them in the live bitmap.
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyAlwaysCollect) {
- BindLiveToMarkBitmap(space);
- }
- }
-
- GetHeap()->GetLargeObjectsSpace()->CopyLiveToMarked();
- }
這個函數定義在文件art/runtime/gc/collector/sticky_mark_sweep.cc中。
StickyMarkSweep類的成員函數BindBitmap除了調用父類PartialMarkSweep類的成員函數BindBitmap將Image Space和Zygote Space標記爲不回收之外,還會將回收策略爲kGcRetentionPolicyAlwaysCollect的Continuous Space的Live Bitmap和Mark Bitmap進行交換,執行同樣操作的還有Large Object Space。從前面ART運行時Java堆創建過程分析一文可以知道,回收策略爲kGcRetentionPolicyAlwaysCollect的Space就是Allocation Space,因此,StickyMarkSweep類就只對Allocation Stack進行GC。
這裏有一點需要注意的是,在交換Live Bitmap和Mark Bitmap之前,Allocation Space和Large Object Space的Live Bitmap都是沒有標記那些上次GC以來分配的對象的,而是記錄在Allocation Stack中,因此,雖然上次GC以來分配的對象都是位於Allocation Space或者Large Object Space上的,但是這裏交換它們的Live Bitmap和Mark Bitmap並不意味着它們不會被回收。
Heap類的成員函數ProcessCards的實現如下所示:
- void Heap::ProcessCards(base::TimingLogger& timings) {
- // Clear cards and keep track of cards cleared in the mod-union table.
- for (const auto& space : continuous_spaces_) {
- if (space->IsImageSpace()) {
- base::TimingLogger::ScopedSplit split("ImageModUnionClearCards", &timings);
- image_mod_union_table_->ClearCards(space);
- } else if (space->IsZygoteSpace()) {
- base::TimingLogger::ScopedSplit split("ZygoteModUnionClearCards", &timings);
- zygote_mod_union_table_->ClearCards(space);
- } else {
- base::TimingLogger::ScopedSplit split("AllocSpaceClearCards", &timings);
- // No mod union table for the AllocSpace. Age the cards so that the GC knows that these cards
- // were dirty before the GC started.
- card_table_->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(), VoidFunctor());
- }
- }
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員函數ProcessCards用來處理Card Table裏面的Dirty Card,它是在Marking階段被調用的。回憶前面Dalvik虛擬機垃圾收集(GC)過程分析一文,在Dalvik虛擬機的垃圾收集過程中,Dirty Card是在Handle Dirty Object階段才處理的,那爲什麼ART運行時會放在Marking階段就進行處理呢?實際上,ART運行時在Handle Dirty階段也會對Dirty Card進行處理。也就是說,在ART運行時的垃圾收集過程中,Dirty Card一共被處理兩次,一次在Marking階段,另一次在Handle Dirty Object階段。這樣做有兩個原因。
第一個原因是在ART運行時中,Card Table在並行和非並行GC中都會用到,因此就不能只在Handle Dirty Object階段才處理。ART運行時中的Card Table在運行期間一直都是用來記錄那些修改了引用類型的成員變量的對象的,即這些對象對應的Card都會設置爲DIRTY。這些Dirty Card在每次GC時都會進行老化處理。老化處理是通過一個叫AgeCardVisitor的類進行的。每一個Card只有三個可能的值,分別是DIRTY、(DIRTY - 1)和0。當一個對象的引用類型的成員變量被修改之後,它對應的Card的值就會被設置爲DIRTY。在接下來的一次GC中,值爲DIRTY的Card就會老化爲(DIRTY - 1)。在又接下來的GC中,值爲(DIRTY-1)的Card就會老化爲0。對於值等於DIRTY和(DIRTY - 1)的Card對應的對象,它們在Marking階段都會被標記。這些對象被標記就意味着它們不會被回收,即使它們不在根集中,並且也不被根集對象引用。儘管這樣會造成一些垃圾對象沒有及時回收,不過這不會引起程序錯誤,並且這些對象在再接下來的一次GC中,如果仍然不在根集中,或者不被根集對象引用,那麼它們就一定會被回收,因爲它們對應的Card已經老化爲0了。
第二個原因是與並行GC相關的。我們知道,Handle Dirty Object階段是在掛起ART運行時線程的前提下進行的,因此,如果把所有的Dirty Card都放在Handle Dirty Object階段處理,那麼就會可能會造成應用程序停頓時間過長。於是,ART運行時就在並行Marking階段也幫忙着處理Dirty Card,通過這種方式儘量減少在Handle Dirty Object階段需要處理的Dirty Card,以達到減少應用程序因爲GC造成的停頓時間。不過這樣就會有一個問題,在Handle Dirty Object階段,如何知道哪些Dirty Card是在並行Marking階段已經被處理過的?這就要藉助Mod Union Table了。在Marking階段,當一個Dirty Card被處理過後,它的值就會由DIRTY變成(DIRTY - 1),並且被它引用的對象都會被記錄在Mod Union Table中。這樣我們就可以在Marking階段和Handle Dirty Object階段做到共用同一個Card Table,而且又能夠區分不同的階段出現的Dirty Card。
有了上面的背景知識之後,我們就可以繼續分析在Marking階段與Card Table和Mod Union Table處理相關的邏輯了,即Heap類的成員函數ProcessCards和UpdateAndMarkModUnion的實現。
從前面ART運行時Java堆創建過程分析一文可以知道,Heap類的成員變量image_mod_union_table_指向的是一個ModUnionTableToZygoteAllocspace對象,用來記錄在Image Space上分配的對象對在Zygote Space和Allocation Space上分配的對象的引用,另外一個成員變量zygote_mod_union_table_則指向一個ModUnionTableCardCache,用來記錄在Zygote Space上分配的對象對在Allocation Space上分配的對象的引用。因此,對於與Image Space和Zygote Space對應的Dirty Card的處理,是分別通過調用ModUnionTableToZygoteAllocspace類和ModUnionTableCardCache類的成員函數ClearCards來進行的。對於Allocation Space,它沒有對應的Mod Union Table,因此,與它對應的Dirty Card的處理,就直接在Card Table進行處理,即調用CardTable類的成員函數ModifyCardsAtomic來進行。
我們首先分析與Image Space對應的Dirty Card在標記階段的處理過程之一,即ModUnionTableToZygoteAllocspace類的成員函數ClearCards的實現,它是從父類ModUnionTableReferenceCache繼承下來的,用來將Dirty Card的值從DIRTY減爲(DIRTY - 1),如下所示:
- void ModUnionTableReferenceCache::ClearCards(space::ContinuousSpace* space) {
- CardTable* card_table = GetHeap()->GetCardTable();
- ModUnionClearCardSetVisitor visitor(&cleared_cards_);
- // Clear dirty cards in the this space and update the corresponding mod-union bits.
- card_table->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(), visitor);
- }
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
參數space指向的是Image Space,ModUnionTableReferenceCache類的成員函數首先找到用來描述ART運行時堆的Card Table,然後再調用它的成員函數ModifyCardsAtomic修改與Image Space對應的那部分Dirty Card的值,具體的修改操作是由第三個參數指定的AgeCardVisitor執行的,第四個參數是一個ModUnionClearCardSetVisitor對象,用來收集緩存那些被修改了的Dirty Card。
CardTable類的成員函數ModifyCardsAtomic的實現如下所示:
- /*
- * Visitor is expected to take in a card and return the new value. When a value is modified, the
- * modify visitor is called.
- * visitor: The visitor which modifies the cards. Returns the new value for a card given an old
- * value.
- * modified: Whenever the visitor modifies a card, this visitor is called on the card. Enables
- * us to know which cards got cleared.
- */
- template <typename Visitor, typename ModifiedVisitor>
- inline void CardTable::ModifyCardsAtomic(byte* scan_begin, byte* scan_end, const Visitor& visitor,
- const ModifiedVisitor& modified) {
- byte* card_cur = CardFromAddr(scan_begin);
- byte* card_end = CardFromAddr(scan_end);
- CheckCardValid(card_cur);
- CheckCardValid(card_end);
-
- // Handle any unaligned cards at the start.
- while (!IsAligned<sizeof(word)>(card_cur) && card_cur < card_end) {
- byte expected, new_value;
- do {
- expected = *card_cur;
- new_value = visitor(expected);
- } while (expected != new_value && UNLIKELY(!byte_cas(expected, new_value, card_cur)));
- if (expected != new_value) {
- modified(card_cur, expected, new_value);
- }
- ++card_cur;
- }
-
- // Handle unaligned cards at the end.
- while (!IsAligned<sizeof(word)>(card_end) && card_end > card_cur) {
- --card_end;
- byte expected, new_value;
- do {
- expected = *card_end;
- new_value = visitor(expected);
- } while (expected != new_value && UNLIKELY(!byte_cas(expected, new_value, card_end)));
- if (expected != new_value) {
- modified(card_cur, expected, new_value);
- }
- }
-
- // Now we have the words, we can process words in parallel.
- uintptr_t* word_cur = reinterpret_cast<uintptr_t*>(card_cur);
- uintptr_t* word_end = reinterpret_cast<uintptr_t*>(card_end);
- uintptr_t expected_word;
- uintptr_t new_word;
-
- // TODO: Parallelize.
- while (word_cur < word_end) {
- while ((expected_word = *word_cur) != 0) {
- new_word =
- (visitor((expected_word >> 0) & 0xFF) << 0) |
- (visitor((expected_word >> 8) & 0xFF) << 8) |
- (visitor((expected_word >> 16) & 0xFF) << 16) |
- (visitor((expected_word >> 24) & 0xFF) << 24);
- if (new_word == expected_word) {
- // No need to do a cas.
- break;
- }
- if (LIKELY(android_atomic_cas(expected_word, new_word,
- reinterpret_cast<int32_t*>(word_cur)) == 0)) {
- for (size_t i = 0; i < sizeof(uintptr_t); ++i) {
- const byte expected_byte = (expected_word >> (8 * i)) & 0xFF;
- const byte new_byte = (new_word >> (8 * i)) & 0xFF;
- if (expected_byte != new_byte) {
- modified(reinterpret_cast<byte*>(word_cur) + i, expected_byte, new_byte);
- }
- }
- break;
- }
- }
- ++word_cur;
- }
- }
這個函數定義在文件art/runtime/gc/accounting/card_table-inl.h中。
CardTable類的成員函數ModifyCardsAtomic看起來代碼不少,但其實它的邏輯是很簡單的。對於參數scan_begin和scan_end描述的Card Table範圍內的每一個Card(佔一個字節),都通過參數visitor描述的一個AgeCardVisitor進行修改。如果修改後的值與原來的值不相等,則將對應的Card交給參數modified描述的一個ModUnionClearCardSetVisitor進行處理。
不過,CardTable類的成員函數ModifyCardsAtomic不是簡單的遍歷參數scan_begin和scan_end描述的Card Table範圍內的每一個Card,它會對去掉指定的Card Table範圍的頭部和尾部不是4字節對齊的部分,得到中間開始地址是以4字節爲邊界、並且長度也是4的倍數的部分。對於不是4字節對齊的頭部和尾部,在當前線程逐個Card的遍歷,而對於中間4字節對齊的部分,則是充分利用編譯器和CPU指令的併發執行特性,以4個字節,即4個Card爲單位進行遍歷。通過這種方式,就可以大大地提高對Card Table的處理過程。
接下來,我們就繼續分析AgeCardVisitor和ModUnionClearCardSetVisitor對傳遞給它們的每一個Card的處理,它們都是通過操作符重載成員函數()來進行處理的。
AgeCardVisitor類的操作符號重載成員函數()的實現如下所示:
- class AgeCardVisitor {
- public:
- byte operator()(byte card) const {
- if (card == accounting::CardTable::kCardDirty) {
- return card - 1;
- } else {
- return 0;
- }
- }
- };
這個函數定義在文件art/runtime/gc/heap.h中。
AgeCardVisitor類的操作符號成員函數()主要就是將原來值等於kCardDirty的Card設置爲(kCardDirty - 1),而其它值都會被設置爲0。
ModUnionClearCardSetVisitor類的操作符重載成員函數()的實現如下所示:
- class ModUnionClearCardSetVisitor {
- public:
- explicit ModUnionClearCardSetVisitor(ModUnionTable::CardSet* const cleared_cards)
- : cleared_cards_(cleared_cards) {
- }
-
- inline void operator()(byte* card, byte expected_value, byte new_value) const {
- if (expected_value == CardTable::kCardDirty) {
- cleared_cards_->insert(card);
- }
- }
-
- private:
- ModUnionTable::CardSet* const cleared_cards_;
- };
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
ModUnionClearCardSetVisitor類的操作符重載成員函數()主要就是將原來值等於kCardDirty的Card保存在成員變量cleared_cards_指向的一個CardSet中。注意,這個CardSet實際上就是前面提到的ModUnionTableReferenceCache類的成員變量cleared_cards_指向的CardSet。
這樣,等到ModUnionTableReferenceCache類的成員函數ClearCards執行完畢,所有與Image Space對應的Dirty Card的值都被減少了1,並且保存在成員變量cleared_cards_指向的CardSet了。
ModUnionTableCardCache類的成員函數ClearCards用來處理與Zygote Space對應的Card Table,它的實現如下所示:
- void ModUnionTableCardCache::ClearCards(space::ContinuousSpace* space) {
- CardTable* card_table = GetHeap()->GetCardTable();
- ModUnionClearCardSetVisitor visitor(&cleared_cards_);
- // Clear dirty cards in the this space and update the corresponding mod-union bits.
- card_table->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(), visitor);
- }
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
從這裏就可以看出,ModUnionTableCardCache類的成員函數ClearCards的實現與我們前面分析的ModUnionTableReferenceCache類的成員函數ClearCards是一樣的,只不過它處理的是與Zygote Space對應的Card Table。
這樣,等到ModUnionTableCardCache類的成員函數ClearCards執行完畢,所有與Zygote Space對應的Dirty Card的值都被減少了1,並且保存在成員變量cleared_cards_指向的CardSet了。
回到前面的Heap類的成員函數ProcessCards中,與Allocation Space對應的Card Table直接通過前面分析過的CardTable類的成員函數ModifyCardsAtomic直接處理。並且在調用CardTable類的成員函數ModifyCardsAtomic的時候,指定的第三個參數同樣是AgeCardVisitor,而第四個參數是一個什麼也不做的VoidFunctor。這意味着與與Allocation Space對應的Dirty Card除了值減少1之外,基它什麼也沒有發生。
上面對Card Table的處理都是在Marking階段開始的時候進行的,在Marking階段結束之前,還會再次調用Heap類的成員函數UpdateAndMarkModUnion對Card Table中的Dirty Card進行處理。
Heap類的成員函數UpdateAndMarkModUnion的實現如下所示:
- void Heap::UpdateAndMarkModUnion(collector::MarkSweep* mark_sweep, base::TimingLogger& timings,
- collector::GcType gc_type) {
- if (gc_type == collector::kGcTypeSticky) {
- // Don't need to do anything for mod union table in this case since we are only scanning dirty
- // cards.
- return;
- }
-
- base::TimingLogger::ScopedSplit split("UpdateModUnionTable", &timings);
- // Update zygote mod union table.
- if (gc_type == collector::kGcTypePartial) {
- base::TimingLogger::ScopedSplit split("UpdateZygoteModUnionTable", &timings);
- zygote_mod_union_table_->Update();
-
- timings.NewSplit("ZygoteMarkReferences");
- zygote_mod_union_table_->MarkReferences(mark_sweep);
- }
-
- // Processes the cards we cleared earlier and adds their objects into the mod-union table.
- timings.NewSplit("UpdateModUnionTable");
- image_mod_union_table_->Update();
-
- // Scans all objects in the mod-union table.
- timings.NewSplit("MarkImageToAllocSpaceReferences");
- image_mod_union_table_->MarkReferences(mark_sweep);
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
對於Sticky Mark Sweep,Card Table裏面的Dirty Card不是在Heap類的成員函數UpdateAndMarkModUnion處理的,而是在Marking階段的最後Marking Reachable Objects過程中處理的。
對於Partial Mark Sweep,需要處理的Card Table包括Image Space和Zygote Space對應的Card Table,而對於Full Mark Sweep,需要處理的Card Table只有Image Space對應的Card Table。
由於Image Space和Zygote Space對應的Card Table中的Dirty Card在前面都已經處理過了,並且它們都已經分別緩存在ModUnionTableToZygoteAllocspace類和ModUnionTableCardCache類的成員變量cleared_cards_指向的CardSet中了,因此,這時候主要對這兩個CardSet中的Dirty Card進行處理就行了。
我們先回顧一下,Dirty Card記錄的是在不需要進行回收的Space上分配的並且在GC過程中類型爲引用的成員變量被修改過的對象,這些被修改的引用類型的成員變量有可能指向了一個在需要進行回收的Space上分配的對象,而這些對象可能不在根集中,因此就需要將將它們當作根集對象一樣進行標記。爲了方便下面的描述,我們將這些需要像根集對象一樣進行標記的對象稱爲被Dirty Card引用的對象。
對於被與Image Space對應的Dirty Card引用的對象,是通過調用ModUnionTableToZygoteAllocspace類的成員函數Update和MarkReferences進行的,它們均是從父類ModUnionTableReferenceCache繼承下來的,因此接下來我們就分析ModUnionTableReferenceCache類的成員函數Update和MarkReferences的實現。
ModUnionTableReferenceCache類的成員函數Update的實現如下所示:
- void ModUnionTableReferenceCache::Update() {
- Heap* heap = GetHeap();
- CardTable* card_table = heap->GetCardTable();
-
- std::vector<const Object*> cards_references;
- ModUnionReferenceVisitor visitor(this, &cards_references);
-
- for (const auto& card : cleared_cards_) {
- // Clear and re-compute alloc space references associated with this card.
- cards_references.clear();
- uintptr_t start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card));
- uintptr_t end = start + CardTable::kCardSize;
- auto* space = heap->FindContinuousSpaceFromObject(reinterpret_cast<Object*>(start), false);
- DCHECK(space != nullptr);
- SpaceBitmap* live_bitmap = space->GetLiveBitmap();
- live_bitmap->VisitMarkedRange(start, end, visitor);
-
- // Update the corresponding references for the card.
- auto found = references_.find(card);
- if (found == references_.end()) {
- if (cards_references.empty()) {
- // No reason to add empty array.
- continue;
- }
- references_.Put(card, cards_references);
- } else {
- found->second = cards_references;
- }
- }
- cleared_cards_.clear();
- }
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
ModUnionTableReferenceCache類的成員函數Update主要就是對前面保存在成員變量cleared_cards_描述的一個CardSet中的每一個Dirty Card進行遍歷,並且找到這些Dirty Card對應的對象,然後再通過ModUnionReferenceVisitor的操作符重載成員函數()對這些對象的引用類型的成員變量引用的對象進行收集,並且保存在另外一個成員變量references_描述的一個SafeMap中。
ModUnionReferenceVisitor類的操作符重載成員函數()的實現如下所示:
- class ModUnionReferenceVisitor {
- public:
- explicit ModUnionReferenceVisitor(ModUnionTableReferenceCache* const mod_union_table,
- std::vector<const Object*>* references)
- : mod_union_table_(mod_union_table),
- references_(references) {
- }
-
- void operator()(const Object* obj) const
- SHARED_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_) {
- DCHECK(obj != NULL);
- // We don't have an early exit since we use the visitor pattern, an early
- // exit should significantly speed this up.
- AddToReferenceArrayVisitor visitor(mod_union_table_, references_);
- collector::MarkSweep::VisitObjectReferences(obj, visitor);
- }
- private:
- ModUnionTableReferenceCache* const mod_union_table_;
- std::vector<const Object*>* const references_;
- };
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
參數obj描述的是便是與Dirty Card對應的對象,現在需要通過MarkSweep類的成員函數VisitObjectReferences遍歷它那些引用類型的成員變量,這些成員變量將被AddToReferenceArrayVisitor類的操作符重載成員函數()進行處理。
AddToReferenceArrayVisitor類的操作符重載成員函數()的實現如下所示:
- class AddToReferenceArrayVisitor {
- public:
- explicit AddToReferenceArrayVisitor(ModUnionTableReferenceCache* mod_union_table,
- std::vector<const Object*>* references)
- : mod_union_table_(mod_union_table),
- references_(references) {
- }
-
- // Extra parameters are required since we use this same visitor signature for checking objects.
- void operator()(const Object* obj, const Object* ref, const MemberOffset& /* offset */,
- bool /* is_static */) const {
- // Only add the reference if it is non null and fits our criteria.
- if (ref != NULL && mod_union_table_->AddReference(obj, ref)) {
- references_->push_back(ref);
- }
- }
-
- private:
- ModUnionTableReferenceCache* const mod_union_table_;
- std::vector<const Object*>* const references_;
- };
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
參數obj是一個與Dirty Card對應的對象,而參數ref是對象obj的一個引用類型的成員變量指向的對象。AddToReferenceArrayVisitor類的操作符重載成員函數()通過調用成員變量mod_union_table_指向的是一個ModUnionTableToZygoteAllocspace對象的成員函數AddReference來判斷是否需要將參數ref指向的對象添加到成員變量references_指向的一個vector中。如果ModUnionTableToZygoteAllocspace類的成員函數AddReference的返回值等於true,那麼就將參數ref指向的對象添加到成員變量references_指向的一個vector中。
ModUnionTableToZygoteAllocspace類的成員函數AddReference的實現如下所示:
- class ModUnionTableToZygoteAllocspace : public ModUnionTableReferenceCache {
- public:
- explicit ModUnionTableToZygoteAllocspace(Heap* heap) : ModUnionTableReferenceCache(heap) {}
-
- bool AddReference(const mirror::Object* /* obj */, const mirror::Object* ref) {
- const std::vector<space::ContinuousSpace*>& spaces = GetHeap()->GetContinuousSpaces();
- typedef std::vector<space::ContinuousSpace*>::const_iterator It;
- for (It it = spaces.begin(); it != spaces.end(); ++it) {
- if ((*it)->Contains(ref)) {
- return (*it)->IsDlMallocSpace();
- }
- }
- // Assume it points to a large object.
- // TODO: Check.
- return true;
- }
- };
這個函數定義在文件art/runtime/gc/accounting/mod_union_table-inl.h中。
ModUnionTableToZygoteAllocspace類的成員函數AddReference檢查參數ref指向的對象,如果它位於一個Continous Space中,並且這個Continuous Space是一個DlMallocSpace,那麼就返回true,以表示參數ref指向的對象是一個在Zygote Space和Allocation Space上分配的對象。注意,Zygote Space和Allocation Space都是屬於DlMallocSpace。
另一方面,如果參數ref指向的對象不是位於Continous Space,那麼它就肯定是位於Large Object Space中,這時候也需要ModUnionTableToZygoteAllocspace類的成員函數AddReference也是返回true,以表示該對象也需要進行處理。
這樣,等到ModUnionTableToZygoteAllocspace類的成員函數Update執行完畢,那些直接被Dirty Card引用的對象就記錄在其成員變量references_描述的一個Safe Map中了,接下來還需要繼續調用ModUnionTableToZygoteAllocspace類的成員函數MarkReferences對保存在上述Safe Map中的對象進行遞歸標記。
ModUnionTableToZygoteAllocspace類的成員函數MarkReferences是從父類ModUnionTableReferenceCache繼承下來的,它的實現如下所示:
- void ModUnionTableReferenceCache::MarkReferences(collector::MarkSweep* mark_sweep) {
- ......
-
- for (const auto& ref : references_) {
- for (const auto& obj : ref.second) {
- mark_sweep->MarkRoot(obj);
- ......
- }
- }
- ......
- }
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
ModUnionTableReferenceCache類的成員函數MarkReferences主要就是對保存在成員變量references_描述的一個Safe Map中的對象通過調用參數mark_sweep描述的一個Mark Sweep進行遞歸標記。
這樣,等ModUnionTableReferenceCache類的成員函數MarkReferences執行完畢,所有被與Image Space對應的Dirty Card引用的對象都被遞歸標記完畢了。
接下來我們再來分析被與Zygote Space對應的Dirty Card引用的對象遞歸標記的過程。如前所述,這是通過調用ModUnionTableCardCache類的成員函數Update和MarkReferences來實現的。
ModUnionTableCardCache類的成員函數Update是一個空實現,它也什麼也不做,因此我們主要就是分析ModUnionTableCardCache類的成員函數MarkReferences的實現,如下所示:
- // Mark all references to the alloc space(s).
- void ModUnionTableCardCache::MarkReferences(collector::MarkSweep* mark_sweep) {
- CardTable* card_table = heap_->GetCardTable();
- ModUnionScanImageRootVisitor visitor(mark_sweep);
- space::ContinuousSpace* space = nullptr;
- SpaceBitmap* bitmap = nullptr;
- for (const byte* card_addr : cleared_cards_) {
- auto start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card_addr));
- auto end = start + CardTable::kCardSize;
- auto obj_start = reinterpret_cast<Object*>(start);
- if (UNLIKELY(space == nullptr || !space->Contains(obj_start))) {
- space = heap_->FindContinuousSpaceFromObject(obj_start, false);
- DCHECK(space != nullptr);
- bitmap = space->GetLiveBitmap();
- DCHECK(bitmap != nullptr);
- }
- bitmap->VisitMarkedRange(start, end, visitor);
- }
- }
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
ModUnionTableCardCache類的成員變量cleared_cards描述的是一具CarSet,這個CardSet保存了與Zygote Space對應的Card Table中的Dirty Card。ModUnionTableCardCache類的成員函數MarkReferences遍歷與這些Dirty Card對應的每一個對象,並且調用ModUnionScanImageRootVisitor類的操作符重載成員函數()對這些對象進行處理。
ModUnionScanImageRootVisitor類的操作符重載成員函數()的實現如下所示:
- class ModUnionScanImageRootVisitor {
- public:
- explicit ModUnionScanImageRootVisitor(collector::MarkSweep* const mark_sweep)
- : mark_sweep_(mark_sweep) {}
-
- void operator()(const Object* root) const
- EXCLUSIVE_LOCKS_REQUIRED(Locks::heap_bitmap_lock_)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- DCHECK(root != NULL);
- mark_sweep_->ScanRoot(root);
- }
-
- private:
- collector::MarkSweep* const mark_sweep_;
- };
這個函數定義在文件art/runtime/gc/accounting/mod_union_table.cc中。
參數root描述的便是與Zygote Space對應的Dirty Card的對象,ModUnionScanImageRootVisitor類的操作符重載成員函數()通過調用成員變量mark_sweep_描述的一個Mark Sweep對象的成員函數ScanRoot對它進行遞歸標記。
從這裏就可以看出,雖然我們說Heap類的成員變量zygote_mod_union_table_描述的ModUnionTableCardCache是用來描述在Zygote Space上分配的對象對在Allocation Space上分配的對象的引用,但是ModUnionTableCardCache類並沒有像ModUnionTableToZygoteAllocspace一樣對被引用對象所在的Space進行判斷,而是不管在什麼Space都進行標記,這樣雖然不會造成邏輯錯誤,但是也會多做一些無用功。例如,如果被引用對象是位於Image Space上,由於Image Space在這種情況下是不進行回收的,那麼對它們進行標記就沒有意義。ART運行時實現了一個ModUnionTableToAllocspace類,它的實現就類似於ModUnionTableToZygoteAllocspace,不過只會對被引用對象是在Allocation Space上分配的纔會進行遞歸標記。
這樣,等ModUnionTableCardCache類的成員函數MarkReferences執行完畢,所有被與Zygote Space對應的Dirty Card引用的對象都被遞歸標記完畢了。
至此,Marking階段對Card Table中的Dirty Card的處理就暫告一段落了,回到前面的MarkSweep類的成員函數MarkingPhase中,接下它還需要做的事情包括調用Heap類的成員函數SwapStacks交換Allocation Stack和Live Stack,以及調用MarkSweep類的成員函數MarkRoots、MarkThreadRoots、MarkNonThreadRoots、MarkConcurrentRoots和MarkReachableObjects來遞歸標記根集對象。
Heap類的成員函數SwapStacks的實現如下所示:
- void Heap::SwapStacks() {
- allocation_stack_.swap(live_stack_);
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員變量allocation_stack_和live_stack_指向的分別是ART運行時的Allocation Stack和Live Stack,Heap類的成員函數SwapStacks對它們進交換,以便後面Sticky Mark Sweep可以進行進一步處理。
接下來,我們再分用來標記根集對象的MarkSweep類的成員函數MarkRoots、MarkThreadRoots、MarkNonThreadRoots、MarkConcurrentRoots。
ART運行時的根集對象按照是否能夠並行訪問劃分爲兩類進行標記,一類是可以需要在獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖下進行標記,而另一類不需要。Runtime類提供了兩個成員函數VisitConcurrentRoots和VisitNonConcurrentRoots來完成這兩個操作。
Runtime類的成員函數VisitConcurrentRoots的實現如下所示:
- void Runtime::VisitConcurrentRoots(RootVisitor* visitor, void* arg, bool only_dirty,
- bool clean_dirty) {
- intern_table_->VisitRoots(visitor, arg, only_dirty, clean_dirty);
- class_linker_->VisitRoots(visitor, arg, only_dirty, clean_dirty);
- }
這個函數定義在文件art/runtime/runtime.cc中。
Runtime類的成員變量intern_table_指向的是一個InternTable對象,裏面保存有ART運行時使用的字符串常量,另外一個成員變量class_linker_指向的是一個ClassLinker對象,裏面保存有ART運行時加載的類對象以及內部使用的Dex Cache。關於Dex Cache,可以參考前面Android運行時ART執行類方法的過程分析一文。
Runtime類的成員函數VisitConcurrentRoots所做的事情就是將ART運行時使用的字符串常量、已經加載的類對象以及Dex Cache對象標記爲根集對象。
Runtime類的成員函數VisitNonConcurrentRoots的實現如下所示:
- void Runtime::VisitNonConcurrentRoots(RootVisitor* visitor, void* arg) {
- thread_list_->VisitRoots(visitor, arg);
- VisitNonThreadRoots(visitor, arg);
- }
這個函數定義在文件art/runtime/runtime.cc中。
Runtime類的成員變量thread_list_指向的是一個ThreadList對象,裏面保存了所有的ART運行時線程,Runtime類的成員函數VisitNonConcurrentRoots通過調用這個ThreadList對象的成員函數VisitRoots來標記所有位於ART運行時線程的調用棧上的根集對象。對於調用棧上的根集對象的標記,可以參考前面Dalvik虛擬機垃圾收集(GC)過程分析一文,ART運行時和Dalivk虛擬機的實現原理都是差不多的,這裏不再詳述。
Runtime類的成員函數VisitNonConcurrentRoots還會調用另外一個成員函數VisitNonConcurrentRoots來標記那些不是位於ART運行時線程的調用棧上的根集對象,它的實現如下所示:
- void Runtime::VisitNonThreadRoots(RootVisitor* visitor, void* arg) {
- java_vm_->VisitRoots(visitor, arg);
- if (pre_allocated_OutOfMemoryError_ != NULL) {
- visitor(pre_allocated_OutOfMemoryError_, arg);
- }
- visitor(resolution_method_, arg);
- for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) {
- visitor(callee_save_methods_[i], arg);
- }
- }
這個函數定義在文件art/runtime/runtime.cc中。
不是位於ART運行時線程的調用棧上的根集對象包括:
A. 在JNI方法裏面通過JNI接口創建的全局引用對象以及被PIN住的數組對象,這些對象保存在Runtime類的成員變量java_vm_描述的一個JavaVMExt對象中,通過調用它的成員函數VisitRoots即可將它們標記爲根集對象。
B. Runtime類的成員pre_allocated_OutOfMemoryError_指向的一個OOM異常對象,直接通過參數visitor描述的一個RootVisitor將它標記爲根集對象。
C. Runtime類內部使用的運行時方法對象,它們分別保存在成員變量resolution_method_和callee_save_methods_中,它們也是接通過參數visitor描述的一個RootVisitor將它標記爲根集對象。。關於ART運行時的運行時方法,可以參考前面Android運行時ART執行類方法的過程分析一文。
此外,Runtime類還提供了另外一個成員函數VisitRoots,用來一次性標記所有的根集對象,如下所示:
- void Runtime::VisitRoots(RootVisitor* visitor, void* arg, bool only_dirty, bool clean_dirty) {
- VisitConcurrentRoots(visitor, arg, only_dirty, clean_dirty);
- VisitNonConcurrentRoots(visitor, arg);
- }
這個函數定義在文件art/runtime/runtime.cc中。
Runtime類的成員函數VisitRoots通過調用前面分析過的兩個成員函數VisitConcurrentRoots和VisitNonConcurrentRoots來遍歷當前ART運行時的所有根集對象。
回到MarkSweep類的成員函數MarkingPhase中,它先標記那些需要獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的根集對象,然後再標記那些不需要獲取該讀寫鎖的根集對象,後者通過調用MarkSweep類的成員函數MarkConcurrentRoots來實現,如下所示:
- void MarkSweep::MarkConcurrentRoots() {
- timings_.StartSplit("MarkConcurrentRoots");
- // Visit all runtime roots and clear dirty flags.
- Runtime::Current()->VisitConcurrentRoots(MarkObjectCallback, this, false, true);
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkConcurrentRoots調用的就是前面分析Runtime類的成員函數VisitConcurrentRoots來標記相應的根集對象,並且指定的標記函數爲MarkSweep類的靜態成員函數MarkObjectCallback。
MarkSweep類的靜態成員函數MarkObjectCallback的實現如下所示:
- void MarkSweep::MarkObjectCallback(const Object* root, void* arg) {
- ......
- MarkSweep* mark_sweep = reinterpret_cast<MarkSweep*>(arg);
- mark_sweep->MarkObjectNonNull(root);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的靜態成員函數MarkObjectCallback又是通過調用另外一個成員函數MarkObjectNonNull來對參數root描述的根集對象進行標記的。
MarkSweep類的成員函數MarkObjectNonNull的實現如下所示:
- inline void MarkSweep::MarkObjectNonNull(const Object* obj) {
- DCHECK(obj != NULL);
-
- if (IsImmune(obj)) {
- DCHECK(IsMarked(obj));
- return;
- }
-
- // Try to take advantage of locality of references within a space, failing this find the space
- // the hard way.
- accounting::SpaceBitmap* object_bitmap = current_mark_bitmap_;
- if (UNLIKELY(!object_bitmap->HasAddress(obj))) {
- accounting::SpaceBitmap* new_bitmap = heap_->GetMarkBitmap()->GetContinuousSpaceBitmap(obj);
- if (LIKELY(new_bitmap != NULL)) {
- object_bitmap = new_bitmap;
- } else {
- MarkLargeObject(obj, true);
- return;
- }
- }
-
- // This object was not previously marked.
- if (!object_bitmap->Test(obj)) {
- object_bitmap->Set(obj);
- if (UNLIKELY(mark_stack_->Size() >= mark_stack_->Capacity())) {
- // Lock is not needed but is here anyways to please annotalysis.
- MutexLock mu(Thread::Current(), mark_stack_lock_);
- ExpandMarkStack();
- }
- // The object must be pushed on to the mark stack.
- mark_stack_->PushBack(const_cast<Object*>(obj));
- }
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkObjectNonNull首先是判斷參數obj指定的對象是否在不回收範圍內,這是通過調用MarkSweep類的成員函數IsImmune來判斷的。GC的不回收範圍,是前面通過調用MarkSweep類的成員函數BindBitmap來設置的。一旦參數obj指定的對象在不回收範圍內,MarkSweep類的成員函數MarkObjectNonNull什麼也不用做就返回了。
MarkSweep類的成員變量current_mark_bitmap_緩存的是Allocation Space對應的Mark Bitmap,這是通過前面分析的MarkSweep類的成員函數FindDefaultMarkBitmap來實現的。這個緩存的Mark Bitmap是爲了避免每次標記對象時,都要去查找它對應的Mark Bitmap而設計的,這樣可以提高標記對象的效率,因爲大部分對象都是位於Allocation Space上的。
MarkSweep類的成員函數MarkObjectNonNull接下來就是判斷成員變量current_mark_bitmap_指向的Mark Bitmap是否可以用來標記對象obj。如果不可以,就先在Continuous Space的Mark Bitmap中找到可以用來標記對象obj的Mark Bitmap。如果找不到,就說明該對象是在Large Object Space上分配的,因此就調用另外一個成員函數MarkLargeObject對它進行標記。
找到了可以用來標記對象obj的Mark Bitmap之後,就可以對對象obj進行處理了。如果對象obj之前已經被標記過了,那麼就什麼也不用做。否則的話,就調用SpaceBitmap類的成員函數Set對它進行標記,並且將它壓入到MarkSweep類的成員變量mark_stack_描述的一個Mark Stack中,以表後面可以執行遞歸標記。
回到MarkSweep類的成員函數MarkingPhase中,對於那些需要獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的根集對象的標記,分爲非並行GC和並行GC兩種情況考慮。
對於非並行GC,此時除了當前執行GC的線程之外,其它的ART運行時線程都已經被掛起,因此,這時候根集對象就只可以在當前執行GC的線程中進行標記。這是通過調用MarkSweep類的成員函數MarkRoots來實現的,如下所示:
- void MarkSweep::MarkRoots() {
- timings_.StartSplit("MarkRoots");
- Runtime::Current()->VisitNonConcurrentRoots(MarkObjectCallback, this);
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkRoots通過調用前面分析過的Runtime類的成員函數VisitNonConcurrentRoots來遍歷那些需要獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的根集對象,並且也是通過調用前面分析過的MarkSweep類的靜態成員函數MarkObjectCallback,對它們進行標記。
對並行GC。我們可以將那些需要獲取Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的根集對象劃分爲兩類,一類是位於線程調用棧的,另一類是不位於線程調用棧的。對於不是位於線程調用棧的根集對象,在當前執行GC的線程中調用MarkSweep類的成員函數MarkNonThreadRoots來進行標記。
MarkSweep類的成員函數MarkNonThreadRoots的實現如下所示:
- void MarkSweep::MarkNonThreadRoots() {
- timings_.StartSplit("MarkNonThreadRoots");
- Runtime::Current()->VisitNonThreadRoots(MarkObjectCallback, this);
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkRoots通過調用前面分析過的Runtime類的成員函數VisitNonThreadRoots來遍歷那些不是在線程調用棧的根集對象,並且也是通過調用前面分析過的MarkSweep類的靜態成員函數MarkObjectCallback,對它們進行標記。
對於位於線程調用棧的根集對象的標記,可以做利用CPU的多核特性做一些併發優化。注意,這時候除了當前執行GC的線程可以運行之外,其它的ART運行時線程也是可以運行的。這樣就可以讓正在運行的ART運行時線程來併發標記各自那些位於調用棧上的根集對象。這個工作是通過調用MarkSweep類的成員函數MarkThreadRoots來實現的,如下所示:
- void MarkSweep::MarkThreadRoots(Thread* self) {
- MarkRootsCheckpoint(self);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkThreadRoots又是通過調用另外一個成員函數MarkRootsCheckpoint對調用棧上的根集對象進行並行標記的,後者的實現如下所示:
- void MarkSweep::MarkRootsCheckpoint(Thread* self) {
- CheckpointMarkThreadRoots check_point(this);
- timings_.StartSplit("MarkRootsCheckpoint");
- ThreadList* thread_list = Runtime::Current()->GetThreadList();
- // Request the check point is run on all threads returning a count of the threads that must
- // run through the barrier including self.
- size_t barrier_count = thread_list->RunCheckpoint(&check_point);
- // Release locks then wait for all mutator threads to pass the barrier.
- // TODO: optimize to not release locks when there are no threads to wait for.
- Locks::heap_bitmap_lock_->ExclusiveUnlock(self);
- Locks::mutator_lock_->SharedUnlock(self);
- ThreadState old_state = self->SetState(kWaitingForCheckPointsToRun);
- CHECK_EQ(old_state, kWaitingPerformingGc);
- gc_barrier_->Increment(self, barrier_count);
- self->SetState(kWaitingPerformingGc);
- Locks::mutator_lock_->SharedLock(self);
- Locks::heap_bitmap_lock_->ExclusiveLock(self);
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkThreadRoots首先是創建一個類型爲CheckpointMarkThreadRoots的Check Point,然後通過調用ThreadList類的成員函數RunCheckpoint將該Check Point提交給當前運行的ART運行時線程執行。
ThreadList類的成員函數RunCheckpoint返回時,只表示Check Point已經提交了,但是可能還沒有全部執行。它的返回值barrier_count描述的是執行Check Point的ART運行時線程的個數。MarkSweep類的成員函數MarkThreadRoots接下來就需要調用成員變量gc_barrier_描述的一個Barrier對象的成員函數Increment來等待上述的barrier_count個ART運行時線程執行完畢剛纔提交的Check Point。
在等待其它ART運行時線程執行Check Point之前,MarkSweep類的成員函數MarkThreadRoots會先釋放當前線程之前獲得的Locks類的靜態成員變量heap_bitmap_lock_描述的一個讀寫鎖的寫訪問,以及Locks類的靜態成員變量mutator_lock_描述的一個讀寫鎖的讀訪問。等到Check Point都執先完成之後,再重新獲得上述的兩個鎖。
ThreadList類的成員函數RunCheckpoint的實現如下所示:
- size_t ThreadList::RunCheckpoint(Closure* checkpoint_function) {
- Thread* self = Thread::Current();
- ......
-
- std::vector<Thread*> suspended_count_modified_threads;
- size_t count = 0;
- {
- // Call a checkpoint function for each thread, threads which are suspend get their checkpoint
- // manually called.
- MutexLock mu(self, *Locks::thread_list_lock_);
- for (const auto& thread : list_) {
- if (thread != self) {
- for (;;) {
- if (thread->RequestCheckpoint(checkpoint_function)) {
- // This thread will run it's checkpoint some time in the near future.
- count++;
- break;
- } else {
- // We are probably suspended, try to make sure that we stay suspended.
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- // The thread switched back to runnable.
- if (thread->GetState() == kRunnable) {
- continue;
- }
- thread->ModifySuspendCount(self, +1, false);
- suspended_count_modified_threads.push_back(thread);
- break;
- }
- }
- }
- }
- }
-
- // Run the checkpoint on ourself while we wait for threads to suspend.
- checkpoint_function->Run(self);
-
- // Run the checkpoint on the suspended threads.
- for (const auto& thread : suspended_count_modified_threads) {
- if (!thread->IsSuspended()) {
- // Wait until the thread is suspended.
- uint64_t start = NanoTime();
- do {
- // Sleep for 100us.
- usleep(100);
- } while (!thread->IsSuspended());
- uint64_t end = NanoTime();
- // Shouldn't need to wait for longer than 1 millisecond.
- const uint64_t threshold = 1;
- if (NsToMs(end - start) > threshold) {
- LOG(INFO) << "Warning: waited longer than " << threshold
- << " ms for thread suspend\n";
- }
- }
- // We know for sure that the thread is suspended at this point.
- thread->RunCheckpointFunction();
- {
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- thread->ModifySuspendCount(self, -1, false);
- }
- }
-
- {
- // Imitate ResumeAll, threads may be waiting on Thread::resume_cond_ since we raised their
- // suspend count. Now the suspend_count_ is lowered so we must do the broadcast.
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- Thread::resume_cond_->Broadcast(self);
- }
-
- // Add one for self.
- return count + suspended_count_modified_threads.size() + 1;
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
ThreadList類的成員函數RunCheckpoint的執行邏輯如下所示:
A. 遍歷成員變量list_中的每一個ART運行時線程,並且通過調用它們的成員函數RequestCheckpoint分別向它們提交參數checkpoint_function的一個Check Point。如果能夠成功提交,那麼Thread類的成員函數RequestCheckpoint就返回true。如果不能成功提交,那麼就可能是線程正處於掛起狀態,這時候就要將線程的掛起計數器增加1,確保繼續掛起,並且將該線程保存在變量suspended_count_modified_threads描述的一個向量中。不過,在增加線程的掛起計數器之前,線程有可能又切換爲運行狀態了,這時候就需要重新調用它的成員函數RequestCheckpoint向它重新提交數checkpoint_function的一個Check Point。
B. 當前線程是執行GC的線程,它也需要執行參數checkpoint_function的Check Point,直接調用它的成員函數Run來執行即可。
C. 接下來需要在當前線程代替那些處於掛起狀態的線程執行提交給它們的、但是它們不能執行的Check Point,這是通過在當前線程調用Thread類的成員函數RunCheckpointFunction來完成的。執行完成之後,需要將這些線程的掛起計數器減少1。注意,必須要確保當前線程是在剛纔保存在變量suspended_count_modified_threads描述的一個向量中的線程是在掛起狀態下代替執行參數checkpoint_function的Check Point的,否則就會造成競爭條件。
D. 前面分析恢復ART運行時線程執行的時候提到,在增加了ART運行時線程的掛數計數器之後,需要通過Thread類的靜態成員變量resume_cond_描述的一個條件變量喚醒被掛起的線程。這裏由於也有同樣的操作,因此也需要通過Thread類的靜態成員變量resume_cond_描述的一個條件變量喚醒被掛起的線程。
E. 返回總共用來執行Check Point的ART運行時線程的個數。
注意,這時候那些成功接收了Check Point的ART運行時線程可能還沒有執行Checkp Point。因此,ThreadList類的成員函數RunCheckpoint的調用方需要有一個等待的過程。
接下來,我們繼續分析Thread類的成員函數RequestCheckpoint的實現,以便可以瞭解給一個線程提交Check Point的過程,它的實現如下所示:
- bool Thread::RequestCheckpoint(Closure* function) {
- CHECK(!ReadFlag(kCheckpointRequest)) << "Already have a pending checkpoint request";
- checkpoint_function_ = function;
- union StateAndFlags old_state_and_flags = state_and_flags_;
- // We must be runnable to request a checkpoint.
- old_state_and_flags.as_struct.state = kRunnable;
- union StateAndFlags new_state_and_flags = old_state_and_flags;
- new_state_and_flags.as_struct.flags |= kCheckpointRequest;
- int succeeded = android_atomic_cmpxchg(old_state_and_flags.as_int, new_state_and_flags.as_int,
- &state_and_flags_.as_int);
- return succeeded == 0;
- }
這個函數定義在文件art/runtime/thread.cc中。
Thread類的成員函數RequestCheckpoint首先將參數function描述的一個Check Point保存在成員變量checkpoint_function_,然後嘗試將線程的狀態設置爲kRunnable,並且將其標記位kCheckpointRequest設置爲1。如果能成功修改線程的狀態和標記位,那麼就可以保證線程接下來可以執行保存在成員變量checkpoint_function_的Check Point。
前面分析ART運行時線程的掛起過程時提到,ART運行時線程在運行的過程中,會週期性地檢查自己的掛起計數器和標誌位,如果發現自己的kCheckpointRequest標記位被設置爲1,那麼它就知道自己有一個Check Point需要等待執行,於是就會調用Thread類的成員函數RunCheckpointFunction來執行該Check Point。
Thread類的成員函數RunCheckpointFunction的實現如下所示:
- void Thread::RunCheckpointFunction() {
- ......
- checkpoint_function_->Run(this);
- ......
- }
這個函數定義在文件art/runtime/thread.cc中。
Thread類的成員函數RunCheckpointFunction通過調用成員變量checkpoint_function_指向的一個Check Point的成員函數Run來執行對應的Check Point。在我們這個情景中,Thread類的成員變量checkpoint_function_指向的是一個CheckpointMarkThreadRoots對象,它的成員函數Run的實現如下所示:
- class CheckpointMarkThreadRoots : public Closure {
- public:
- explicit CheckpointMarkThreadRoots(MarkSweep* mark_sweep) : mark_sweep_(mark_sweep) {}
-
- virtual void Run(Thread* thread) NO_THREAD_SAFETY_ANALYSIS {
- ......
- Thread* self = Thread::Current();
- ......
- thread->VisitRoots(MarkSweep::MarkRootParallelCallback, mark_sweep_);
- ......
- mark_sweep_->GetBarrier().Pass(self);
- }
-
- private:
- MarkSweep* mark_sweep_;
- };
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
當前線程通過調用Thread類的成員函數VisitRoots來遍歷調用棧上的根集對象,並且通過MarkSweep類的靜態成員函數MarkRootParallelCallback對它們進行標記。標記完成後,再調用在前面分析MarkSweep類的成員函數MarkRootsCheckpoint時提到的Barrier對象的成員Pass,以便可以通知有一個線程執行完成自己的Check Point了,這樣就可以使得正在等待所有ART運行時線程都執行完成自己的Check Point的GC線程可以被喚醒繼續往前執行。
MarkSweep類的靜態成員函數MarkRootParallelCallback的實現如下所示:
- void MarkSweep::MarkRootParallelCallback(const Object* root, void* arg) {
- ......
- reinterpret_cast<MarkSweep*>(arg)->MarkObjectNonNullParallel(root);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的靜態成員函數MarkRootParallelCallback通過調用另外一個成員函數MarkObjectNonNullParallel來標記參數root描述的根集對象,後者的實現如下所示:
- inline void MarkSweep::MarkObjectNonNullParallel(const Object* obj) {
- ......
- if (MarkObjectParallel(obj)) {
- MutexLock mu(Thread::Current(), mark_stack_lock_);
- if (UNLIKELY(mark_stack_->Size() >= mark_stack_->Capacity())) {
- ExpandMarkStack();
- }
- // The object must be pushed on to the mark stack.
- mark_stack_->PushBack(const_cast<Object*>(obj));
- }
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkObjectNonNullParallel又調用另外一個成員函數MarkObjectParallel來標記參數obj描述的根集對象。如果標記成功,那麼就將它壓入到成員變量mark_stack_描述的一個Mark Stack中,以便後面可以執行遞歸標記。
MarkSweep類的成員函數MarkObjectParallel的實現如下所示:
- inline bool MarkSweep::MarkObjectParallel(const Object* obj) {
- DCHECK(obj != NULL);
-
- if (IsImmune(obj)) {
- DCHECK(IsMarked(obj));
- return false;
- }
-
- // Try to take advantage of locality of references within a space, failing this find the space
- // the hard way.
- accounting::SpaceBitmap* object_bitmap = current_mark_bitmap_;
- if (UNLIKELY(!object_bitmap->HasAddress(obj))) {
- accounting::SpaceBitmap* new_bitmap = heap_->GetMarkBitmap()->GetContinuousSpaceBitmap(obj);
- if (new_bitmap != NULL) {
- object_bitmap = new_bitmap;
- } else {
- // TODO: Remove the Thread::Current here?
- // TODO: Convert this to some kind of atomic marking?
- MutexLock mu(Thread::Current(), large_object_lock_);
- return MarkLargeObject(obj, true);
- }
- }
-
- // Return true if the object was not previously marked.
- return !object_bitmap->AtomicTestAndSet(obj);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkObjectParallel的實現與前面分析的另外一個成員函數MarkObjectNonNull是差不多的,因此這裏不再詳述。
現在,所有的根集對象終於都標記完成了。回到前面的MarkSweep類的成員函數MarkingPhase中,最後需要做的事情是遞歸標記那些根集對象遞歸引用的其它對象,這是通過調用MarkSweep類的成員函數MarkReachableObjects來實現的,如下所示:
- void MarkSweep::MarkReachableObjects() {
- // Mark everything allocated since the last as GC live so that we can sweep concurrently,
- // knowing that new allocations won't be marked as live.
- timings_.StartSplit("MarkStackAsLive");
- accounting::ObjectStack* live_stack = heap_->GetLiveStack();
- heap_->MarkAllocStack(heap_->alloc_space_->GetLiveBitmap(),
- heap_->large_object_space_->GetLiveObjects(), live_stack);
- live_stack->Reset();
- timings_.EndSplit();
- // Recursively mark all the non-image bits set in the mark bitmap.
- RecursiveMark();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數MarkReachableObjects首先會將Live Stack的對象全部進行標記。這裏的Live Stack其實就是之前的Allocation Stack,也就是說,Mark Sweep不會對上次GC以後分配的對象進行垃圾回收。Partial Mark Sweep也是通過父類MarkSweep類的成員函數MarkReachableObjects來遞歸標記根集對象引用的對象的,也就是說,Mark Sweep也不會對上次GC以後分配的對象進行垃圾回收。由於Sticky Mark Sweep剛好相反,它要對上次GC以後分配的對象進行垃圾回收,因此,它就必須要重寫MarkSweep類的成員函數MarkReachableObjects。
MarkSweep類的成員函數MarkReachableObjects接着再調用MarkSweep類的成員函數RecursiveMark對根集對象引用的其它對象進行遞歸標記,它的實現如下所示:
- // Populates the mark stack based on the set of marked objects and
- // recursively marks until the mark stack is emptied.
- void MarkSweep::RecursiveMark() {
- base::TimingLogger::ScopedSplit split("RecursiveMark", &timings_);
- ......
-
- if (kUseRecursiveMark) {
- const bool partial = GetGcType() == kGcTypePartial;
- ScanObjectVisitor scan_visitor(this);
- auto* self = Thread::Current();
- ThreadPool* thread_pool = heap_->GetThreadPool();
- size_t thread_count = GetThreadCount(false);
- const bool parallel = kParallelRecursiveMark && thread_count > 1;
- mark_stack_->Reset();
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- if ((space->GetGcRetentionPolicy() == space::kGcRetentionPolicyAlwaysCollect) ||
- (!partial && space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect)) {
- current_mark_bitmap_ = space->GetMarkBitmap();
- ......
- if (parallel) {
- // We will use the mark stack the future.
- // CHECK(mark_stack_->IsEmpty());
- // This function does not handle heap end increasing, so we must use the space end.
- uintptr_t begin = reinterpret_cast<uintptr_t>(space->Begin());
- uintptr_t end = reinterpret_cast<uintptr_t>(space->End());
- atomic_finger_ = static_cast<int32_t>(0xFFFFFFFF);
-
- // Create a few worker tasks.
- const size_t n = thread_count * 2;
- while (begin != end) {
- uintptr_t start = begin;
- uintptr_t delta = (end - begin) / n;
- delta = RoundUp(delta, KB);
- if (delta < 16 * KB) delta = end - begin;
- begin += delta;
- auto* task = new RecursiveMarkTask(thread_pool, this, current_mark_bitmap_, start,
- begin);
- thread_pool->AddTask(self, task);
- }
- thread_pool->SetMaxActiveWorkers(thread_count - 1);
- thread_pool->StartWorkers(self);
- thread_pool->Wait(self, true, true);
- thread_pool->StopWorkers(self);
- } else {
- // This function does not handle heap end increasing, so we must use the space end.
- uintptr_t begin = reinterpret_cast<uintptr_t>(space->Begin());
- uintptr_t end = reinterpret_cast<uintptr_t>(space->End());
- current_mark_bitmap_->VisitMarkedRange(begin, end, scan_visitor);
- }
- }
- }
- }
- ProcessMarkStack(false);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
如果常量kUseRecursiveMark和kParallelRecursiveMark的值均設置爲true,並且ART運行時在啓動時創建的用於執行併發GC的線程個數thread_count大於1,那麼對於Allocation Space以及不是kGcTypePartial類型的GC下的Zygote Space,將採用多線程併發遞歸標記那些被根集對象直接或者間接引用的其它對象。
假設一個Space的大小爲(end - begin),用來執行併發標記的線程的個數爲thread_count,那麼就將整個Space的標記任務劃分爲(end - begin) / (thread_count * 2)個子任務。每一個子任務都封裝成一個RecursiveMarkTask對象,通過調用ThreadPool類的成員函數AddTask添加到線程池中去。
接着再通過ThreadPool類的成員函數SetMaxActiveWorkers將線程池用於執行任務的線程個數設置(thread_count - 1)。爲什麼不是設置爲thread_count呢?這是因爲當前線程也是屬於執行GC任務的線程之一,因此就只需要從線程池中抽取(thread_count - 1)個線程出來就行了。另外,調用MarkSweep類的成員函數GetThreadCount獲得線程池的線程個數,如果指定的參數爲false,表示要獲取的是在GC併發階段可以用來執行GC任務的線程的個數,對應於ART運運時啓動選項-XX:ConcGCThreads指定的線程個數。如果指定的參數爲true,則表示要獲取的是在GC暫停階段可以用來執行GC任務的線程的個數,對應於ART運運時啓動選項-XX:ParallelGCThreads指定的線程個數。
最後就調用ThreadPool類的成員函數StartWorkers啓動線程池執行任務,並且通過調用ThreadPool類的另外一個成員函數Wait來讓當前線程去執行線程池的任務,並且等待其它線程執行完成任務。
如果一個Space不能滿足上述的併發標記條件,那麼就只能在當前線程中遞歸標記根集對象,這是通過調用SpaceBitmap類的成員函數VisitMarkedRange來完成的,並且通過變量scan_visitor描述的一個ScanObjectVisitor對象來執行具體的標記任務。
由於在前面的標記過程中,可能又會有新的對象被壓入到Mark Stack中,因此,MarkSweep類的成員函數RecursiveMark最後還需要調用另外一個成員函數ProcessMarkStack來檢查Mark Stack是否不爲空。如果不爲空,那麼就需要遞歸標記下去。
這樣,當常量kUseRecursive等於true,MarkMarkSweep類的成員函數RecursiveMark實際上既需要通過線程池來併發執行RecursiveMarkTask,也需要通過調用MarkSweep類的成員函數ProcessMarkStack來執行在單線程中執行遞歸標記根集的任務,而當kUseRecursive等於false,就只會調用MarkSweep類的成員函數ProcessMarkStack來執行在單線程中執行遞歸標記根集的任務。接下來我們就這兩種遞歸標記根集對象的過程。
併發遞歸標記根集對象的過程是通過在線程池中執行RecursiveMarkTask來完成的,也就是調用RecursiveMarkTask類的成員函數Run來完成的,它的實現如下所示:
- class RecursiveMarkTask : public MarkStackTask<false> {
- ......
-
- // Scans all of the objects
- virtual void Run(Thread* self) NO_THREAD_SAFETY_ANALYSIS {
- ScanObjectParallelVisitor visitor(this);
- bitmap_->VisitMarkedRange(begin_, end_, visitor);
- // Finish by emptying our local mark stack.
- MarkStackTask::Run(self);
- }
- };
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
RecursiveMarkTask類的成員函數Run首先是通過調用成員變量bitmap_指向的一個SpaceBitmap對象的成員函數VisitMarkedRange來遍歷成員變量begin_和end_限定範圍的被標記對象,即根集對象,並且通過調用ScanObjectParallelVisitor類的操作符重載函數()對它們進行處理,接着再調用父類MarkStackTask的成員函數Run來遞歸標記上述的根集對象。
ScanObjectParallelVisitor類的操作符重載函數()的實現如下所示:
- template <bool kUseFinger = false>
- class MarkStackTask : public Task {
- ......
-
- protected:
- class ScanObjectParallelVisitor {
- public:
- explicit ScanObjectParallelVisitor(MarkStackTask<kUseFinger>* chunk_task) ALWAYS_INLINE
- : chunk_task_(chunk_task) {}
-
- void operator()(const Object* obj) const {
- MarkSweep* mark_sweep = chunk_task_->mark_sweep_;
- mark_sweep->ScanObjectVisit(obj,
- [mark_sweep, this](const Object* /* obj */, const Object* ref,
- const MemberOffset& /* offset */, bool /* is_static */) ALWAYS_INLINE {
- if (ref != nullptr && mark_sweep->MarkObjectParallel(ref)) {
- ......
- chunk_task_->MarkStackPush(ref);
- }
- });
- }
-
- private:
- MarkStackTask<kUseFinger>* const chunk_task_;
- };
-
- .....
-
- void MarkStackPush(const Object* obj) ALWAYS_INLINE {
- if (UNLIKELY(mark_stack_pos_ == kMaxSize)) {
- // Mark stack overflow, give 1/2 the stack to the thread pool as a new work task.
- mark_stack_pos_ /= 2;
- auto* task = new MarkStackTask(thread_pool_, mark_sweep_, kMaxSize - mark_stack_pos_,
- mark_stack_ + mark_stack_pos_);
- thread_pool_->AddTask(Thread::Current(), task);
- }
- ......
- mark_stack_[mark_stack_pos_++] = obj;
- }
-
- ......
- };
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
ScanObjectParallelVisitor類的操作符重載函數()調用MarkSweep類的成員函數ScanObjectVisit遍歷參數obj指向的一個對象的引用類型的成員變量引用的對象,並且通過一個閉包函數來處理這些引用對象。閉包函數又是通過調用我們前面分析過的MarkSweep類的成員函數MarkObjectParallel來對對象obj的引用類型的成員變量引用的對象。如果這些引用對象之前沒有被標記過,那麼MarkSweep類的成員函數MarkObjectParallel就會返回true,這時候閉包函數將會調用MarkStackTask類的成員函數MarkStack將引用對象壓入到其成員變量mark_stack_描述的一個Mark Stack去。注意,這個Mark Stack是MarkStackTask類在內部使用的一個Stack,它與ART運行時在啓動時創建的Mark Stack是不一樣的。
此外,在將一個對象壓入到MarkStackTask類的成員變量mark_stack_描述的一個Mark Stack去之前,如果這個Mark Stack已經滿了,那麼就會將這個Mark Stack的後半部分還未來得及遞歸標記的對象封裝成一個MarkStackTask添加到線程池去執行遞歸標記。這樣就可以將Mark Stack的大小減半,從而得以將參數obj指向的對象壓入到Mark Stack中。
回到RecursiveMarkTask類的成員函數Run中,它最後通過調過調用父類MarkStackTask的成員函數Run來遞歸標記保存其成員變量mark_stack_描述的Mark Stack中的對象,它的實現如下所示:
- template <bool kUseFinger = false>
- class MarkStackTask : public Task {
- ......
-
- // Scans all of the objects
- virtual void Run(Thread* self) {
- ScanObjectParallelVisitor visitor(this);
- // TODO: Tune this.
- static const size_t kFifoSize = 4;
- BoundedFifoPowerOfTwo<const Object*, kFifoSize> prefetch_fifo;
- for (;;) {
- const Object* obj = NULL;
- if (kUseMarkStackPrefetch) {
- while (mark_stack_pos_ != 0 && prefetch_fifo.size() < kFifoSize) {
- const Object* obj = mark_stack_[--mark_stack_pos_];
- DCHECK(obj != NULL);
- __builtin_prefetch(obj);
- prefetch_fifo.push_back(obj);
- }
- if (UNLIKELY(prefetch_fifo.empty())) {
- break;
- }
- obj = prefetch_fifo.front();
- prefetch_fifo.pop_front();
- } else {
- if (UNLIKELY(mark_stack_pos_ == 0)) {
- break;
- }
- obj = mark_stack_[--mark_stack_pos_];
- }
- DCHECK(obj != NULL);
- visitor(obj);
- }
- }
- };
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
如果常量kUseMarkStackPrefetch的值定義爲true,那麼MarkStackTask的成員函數Run在遞歸遍歷其成員變量mark_stack_描述的一個Mark Stack的對象,會通過函數__builtin_prefetch來預讀取接下來要遍歷的對象,這樣就可以加快從Mark Stack中獲取下一個要遍歷的對象的速度。否則的話,就每次都正常地從Mark Stack讀取下一個要遍歷的對象。
對於從Mark Stack讀取出來的對象,都是通過前面分析的ScanObjectParallelVisitor類的操作符重載成員函數()來進行標記的。由於ScanObjectParallelVisitor類的操作符重載成員函數()又會將它標記的對象引用的其它對象壓入到MarkStackTask的成員變量mark_stack_描述的Mark Stack中,因此,MarkStackTask的成員函數Run就會一直執行,直到上述的Mark Stack爲空爲止。通過這種方式,就完成了遞歸標記根集對象的過程。
這樣,併發遞歸標記根集對象的過程就分析完成了,接下來再來看非併發遞歸標記根集對象的過程,這是通過調用MarkSweep類的成員函數ProcessMarkStack來實現的,如下所示:
- void MarkSweep::ProcessMarkStack(bool paused) {
- timings_.StartSplit("ProcessMarkStack");
- size_t thread_count = GetThreadCount(paused);
- if (kParallelProcessMarkStack && thread_count > 1 &&
- mark_stack_->Size() >= kMinimumParallelMarkStackSize) {
- ProcessMarkStackParallel(thread_count);
- } else {
- // TODO: Tune this.
- static const size_t kFifoSize = 4;
- BoundedFifoPowerOfTwo<const Object*, kFifoSize> prefetch_fifo;
- for (;;) {
- const Object* obj = NULL;
- if (kUseMarkStackPrefetch) {
- while (!mark_stack_->IsEmpty() && prefetch_fifo.size() < kFifoSize) {
- const Object* obj = mark_stack_->PopBack();
- DCHECK(obj != NULL);
- __builtin_prefetch(obj);
- prefetch_fifo.push_back(obj);
- }
- if (prefetch_fifo.empty()) {
- break;
- }
- obj = prefetch_fifo.front();
- prefetch_fifo.pop_front();
- } else {
- if (mark_stack_->IsEmpty()) {
- break;
- }
- obj = mark_stack_->PopBack();
- }
- DCHECK(obj != NULL);
- ScanObject(obj);
- }
- }
- timings_.EndSplit();
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
如果常量kParallelProcessMarkStack的值等於true,並且ART運行時啓動時創建的用來執行併發GC任務的線程池包含的線程個數大於1,以及此時ART運行時的Mark Stack包含的未處理對象的個數大於常量kMinimumParallelMarkStackSize定義的值,那麼MarkSweep類的成員函數ProcessMarkStack就會通過調用另外一個成員函數ProcessMarkStackParallel來併發遞歸標記Mark Stack中的對象。
否則的話,就只會在當前線程中對ART運行時的Mark Stack中的對象進行遞歸標記。在遍歷保存在ART運行時的Mark Stack中的對象時候,同樣使用了前面提到的預讀取技術,這樣就可以加快從ART運行時的Mark Stack中讀取下一個要遍歷的對象的速度。同時,對於保存在ART運行時的Mark Stack中的每一個對象,都是通過調用MarkSweep類的成員函數ScanObject進行標記的。MarkSweep類的成員函數ScanObject在標記一個對象的時候,同時也會將該對象引用的其它的還沒有被標記的對象壓入到ART運行時的Mark Stack中,這樣,上述的遞歸標記過程就會一直執行下去直到ART運行時的Mark Stack爲空爲止。
接下來我們再來看MarkSweep類的成員函數ProcessMarkStackParallel的實現,以便可以瞭解它是如何併發遞歸標記保存在ART運行時的Mark Stack中的對象的,它的實現如下所示:
- void MarkSweep::ProcessMarkStackParallel(size_t thread_count) {
- Thread* self = Thread::Current();
- ThreadPool* thread_pool = GetHeap()->GetThreadPool();
- const size_t chunk_size = std::min(mark_stack_->Size() / thread_count + 1,
- static_cast<size_t>(MarkStackTask<false>::kMaxSize));
- CHECK_GT(chunk_size, 0U);
- // Split the current mark stack up into work tasks.
- for (mirror::Object **it = mark_stack_->Begin(), **end = mark_stack_->End(); it < end; ) {
- const size_t delta = std::min(static_cast<size_t>(end - it), chunk_size);
- thread_pool->AddTask(self, new MarkStackTask<false>(thread_pool, this, delta,
- const_cast<const mirror::Object**>(it)));
- it += delta;
- }
- thread_pool->SetMaxActiveWorkers(thread_count - 1);
- thread_pool->StartWorkers(self);
- thread_pool->Wait(self, true, true);
- thread_pool->StopWorkers(self);
- mark_stack_->Reset();
- CHECK_EQ(work_chunks_created_, work_chunks_deleted_) << " some of the work chunks were leaked";
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數ProcessMarkStackParallel併發遞歸標記ART運行時的Mark Stack的方法與前面分析的MarkSweep類的成員函數RecursiveMark併發遞歸一個Space的根集對象的方法是類似的,只不過前者是對Mark Stack進行任務劃分,而後者是對Space進行任務劃分,兩者劃分出來的都是通過線程池來執行的。
這樣,遞歸標記根集對象的過程也分析完成了。不過,這個過程只適用於Mark Sweep和Partial Mark Sweep。對於Sticky Mark Sweep,前面提到,它必須要重寫了父類MarkSweep的成員函數MarkReachableObjects,以便可以對上次GC以來分配的對象進行垃圾回收。
StickyMarkSweep類的成員函數MarkReachableObjects的實現如下所示:
- void StickyMarkSweep::MarkReachableObjects() {
- // All reachable objects must be referenced by a root or a dirty card, so we can clear the mark
- // stack here since all objects in the mark stack will get scanned by the card scanning anyways.
- // TODO: Not put these objects in the mark stack in the first place.
- mark_stack_->Reset();
- RecursiveMarkDirtyObjects(false, accounting::CardTable::kCardDirty - 1);
- }
這個函數定義在文件art/runtime/gc/collector/sticky_mark_sweep.cc中。
前面雖然我們說,在GC的Marking階段,要標記的對象包括根集對象,以及根集對象可達的對象,但是由於Partial Mark Sweep和Sticky Mark Sweep的存在,從更廣義來說,在GC的Marking階段,要標記的對象包括根集對象,以及根集對象和Dirty Card可達的對象。
對於Sticky Mark Sweep來說,在調用StickyMarkSweep類的成員函數MarkReachableObjects之前,根集對象已經標記好了,並且Dirty Card也已經老化處理過了,但是它們可達的對象還沒有處理。此外,這時候ART運行時的Mark Stack記錄了根集對象直接引用的對象。理論上說,我們需要繼續遞歸標記Mark Stack中的對象,以及Dirty Card引用的對象。但是這裏卻沒有這樣做,而是將Mark Stack清空,只遞歸標記Dirty Card可達的對象。爲什麼可以這樣做呢?
前面我們在分析Heap類的成員函數ClearCards時提到,ART運行時的Card Table在運行期間是一直使用的,也就是它一直記錄着上次GC以來那些修改了引用類型的成員變量的對象。現在考慮Sticky Mark Sweep,它要回收的是Allocation Stack的垃圾對象,並且在標記開始的時候,Image Space、Zygote Space和Allocation Space上次GC後存活下來的對象都已經被標記過了。這時候被標記的根集對象,可以劃分爲有引用類型和沒有引用類型成員變量兩類。
對於有引用類型成員變量的根集對象,它們在設置這些成員變量的時候,會被記錄在Card Table中,即與它們對應的Card是Dirty的。因此,我們就可以在遞歸標記Dirty Card引用的對象的時候,把這些有引用類型成員變量的根集對象也順帶遞歸標記了。
對於沒有引用類型成員變量的根集對象,只需要標記好它們自己就行了,不用遞歸標記它們引用的對象。也就是說,這一類根集對象已經在調用StickyMarkSweep類的成員函數MarkReachableObjects之前處理好了。
因此,StickyMarkSweep類的成員函數MarkReachableObjects就可以在遞歸標記Dirty Card引用的對象之前,將ART運行時的Mark Stack清空。那爲什麼Mark Sweep和Partial Mark Sweep又不可以這樣做呢?
回憶前面分析的Heap類的成員函數UpdateAndMarkModUnion,對於Sticky Mark Sweep,什麼也沒有做,但是Mark Sweep和Partial Mark Sweep卻對Dirty Card直接引用的對象進行了標記。在標記的過程中,ART運行時的Mark Stack也記錄了這些直接被Dirty Card引用的對象引用的其它對象。也就是說,對於Mark Sweep和Partial Mark Sweep來說,此時ART運行時的Mark Stack包含了根集對象和Dirty Card可直達的對象,因此就不能將Mark Stack清空。
對於Sticky Mark Sweep,由於此時Dirty Card只被老化處理過,而Dirty Card引用的對象還沒有被標記過,因此,這時候需要做的就是遞歸標記那些值等於DIRTY和(DIRTY - 1)的Card,這是通過調用從父類MarkSweep繼承下來的成員函數RecursiveMarkDirtyObjects來實現的。
MarkSweep類的成員函數RecursiveMarkDirtyObjects的實現如下所示:
- void MarkSweep::RecursiveMarkDirtyObjects(bool paused, byte minimum_age) {
- ScanGrayObjects(paused, minimum_age);
- ProcessMarkStack(paused);
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數RecursiveMarkDirtyObjects首先是調用成員函數ScanGrayObjects標記值大於等於minimum_age的Dirty Card,也就是值大於等於(DIRTY - 1)的Card引用的對象。由於在標記這些對象的過程中,可能會新的對象被壓入到ART運行時的Mark Stack中,因此接下來還要繼續調用前面已經分析過的MarkSweep類的成員函數ProcessMarkStack來遞歸標記它們。
接下來我們就繼續分析MarkSweep類的成員函數ScanGrayObjects的實現,如下所示:
- void MarkSweep::ScanGrayObjects(bool paused, byte minimum_age) {
- accounting::CardTable* card_table = GetHeap()->GetCardTable();
- ThreadPool* thread_pool = GetHeap()->GetThreadPool();
- size_t thread_count = GetThreadCount(paused);
- // The parallel version with only one thread is faster for card scanning, TODO: fix.
- if (kParallelCardScan && thread_count > 0) {
- Thread* self = Thread::Current();
- // Can't have a different split for each space since multiple spaces can have their cards being
- // scanned at the same time.
- timings_.StartSplit(paused ? "(Paused)ScanGrayObjects" : "ScanGrayObjects");
- // Try to take some of the mark stack since we can pass this off to the worker tasks.
- const Object** mark_stack_begin = const_cast<const Object**>(mark_stack_->Begin());
- const Object** mark_stack_end = const_cast<const Object**>(mark_stack_->End());
- const size_t mark_stack_size = mark_stack_end - mark_stack_begin;
- // Estimated number of work tasks we will create.
- const size_t mark_stack_tasks = GetHeap()->GetContinuousSpaces().size() * thread_count;
- DCHECK_NE(mark_stack_tasks, 0U);
- const size_t mark_stack_delta = std::min(CardScanTask::kMaxSize / 2,
- mark_stack_size / mark_stack_tasks + 1);
- size_t ref_card_count = 0;
- cards_scanned_ = 0;
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- byte* card_begin = space->Begin();
- byte* card_end = space->End();
- // Calculate how many bytes of heap we will scan,
- const size_t address_range = card_end - card_begin;
- // Calculate how much address range each task gets.
- const size_t card_delta = RoundUp(address_range / thread_count + 1,
- accounting::CardTable::kCardSize);
- // Create the worker tasks for this space.
- while (card_begin != card_end) {
- // Add a range of cards.
- size_t addr_remaining = card_end - card_begin;
- size_t card_increment = std::min(card_delta, addr_remaining);
- // Take from the back of the mark stack.
- size_t mark_stack_remaining = mark_stack_end - mark_stack_begin;
- size_t mark_stack_increment = std::min(mark_stack_delta, mark_stack_remaining);
- mark_stack_end -= mark_stack_increment;
- mark_stack_->PopBackCount(static_cast<int32_t>(mark_stack_increment));
- DCHECK_EQ(mark_stack_end, mark_stack_->End());
- // Add the new task to the thread pool.
- auto* task = new CardScanTask(thread_pool, this, space->GetMarkBitmap(), card_begin,
- card_begin + card_increment, minimum_age,
- mark_stack_increment, mark_stack_end);
- thread_pool->AddTask(self, task);
- card_begin += card_increment;
- }
-
- ......
- }
- thread_pool->SetMaxActiveWorkers(thread_count - 1);
- thread_pool->StartWorkers(self);
- thread_pool->Wait(self, true, true);
- thread_pool->StopWorkers(self);
- ......
- } else {
- for (const auto& space : GetHeap()->GetContinuousSpaces()) {
- ......
- ScanObjectVisitor visitor(this);
- card_table->Scan(space->GetMarkBitmap(), space->Begin(), space->End(), visitor, minimum_age);
- ......
- }
- }
- }
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
如果常量kParallelCardScan的值設置爲true,並且ART運行時啓動過程中創建的用於執行並行GC任務的線程池包含的線程個數大於0,那麼就採用併發的方法遞歸標記值Card Table中值等於DIRTY和(DIRTY - 1)的Card引用的對象。否則的話,就在當前線程中調用CardTable類的成員函數Scan來標記值Card Table中值等於DIRTY和(DIRTY - 1)的Card引用的對象。
使用併發方法遞歸標記Dirty Card引用的對象時,按照Card的大小和可用於執行併發GC任務的線程數來劃分子任務。劃分後每一個子任務都使用一個CardScanTask來描述。CardScanTask除了能夠用來遞歸標記Dirty Card引用的對象之外,還能同時遞歸標記Mark Stack中的對象。Mark Stack的遞歸標記任務是按照Card的大小、可用於執行併發GC任務的線程數和Mark Stack的大小來劃分的。CardScanTask任務劃分完成之後,就將它們添加到線程池中去併發執行。它們的執行過程與我們前面分析的RecursiveMarkTask任務的併發執行過程是類似的,因此這裏就不再深入分析下去。
這樣,Sticky Mark Sweep遞歸標記根集對象和Dirty Card引用的對象的過程也分析完成了。至此,ART運行時的GC標記階段就執行完畢。接下來我繼續分析並行GC的Handle Dirty Object階段。
5. HandleDirtyObjectsPhase。
MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的Handle Dirty Object階段都是以MarkSweep類的成員函數HandleDirtyObjectsPhase爲入口,它的實現如下所示:
- bool MarkSweep::HandleDirtyObjectsPhase() {
- base::TimingLogger::ScopedSplit split("HandleDirtyObjectsPhase", &timings_);
- Thread* self = Thread::Current();
- Locks::mutator_lock_->AssertExclusiveHeld(self);
-
- {
- WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
-
- // Re-mark root set.
- ReMarkRoots();
-
- // Scan dirty objects, this is only required if we are not doing concurrent GC.
- RecursiveMarkDirtyObjects(true, accounting::CardTable::kCardDirty);
- }
-
- ProcessReferences(self);