[原文出處------------------ART運行時垃圾收集(GC)過程分析](http://blog.csdn.net/luoshengyang/article/details/42555483)
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類的成員函數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來完成的,它的實現如下所示:
[cpp] view plaincopy在CODE上查看代碼片派生到我的代碼片
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);
......
// Disallow new system weaks to prevent a race which occurs when someone adds a new system
// weak before we sweep them. Since this new system weak may not be marked, the GC may
// incorrectly sweep it. This also fixes a race where interning may attempt to return a strong
// reference to a string that is about to be swept.
Runtime::Current()->DisallowNewSystemWeaks();
return true;
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數HandleDirtyObjectsPhase主要執行以下操作:
* A. 調用成員函數ReMarkRoots重新標記根集對象。
* B. 調用成員函數RecursiveMarkDirtyObjects遞歸標記值等于Card Table中值等于DIRTY的Card。注意,此時值等于(DIRTY - 1)的Card已經在標記階段處理過了,因此這里不需要再對它們進行處理。
* C. 調用成員函數ProcessReferences處理Soft Reference、Weak Reference、Phantom Reference和Finalizer Reference等引用對象。
* D. 調用Runtime類的成員函數DisallowNewSystemWeaks禁止在系統內部分配全局弱引用對象、Monitor對象和常量字符池對象等,因此這些新分配的對象會得不到標記,從而會導致在接下來的清除階段中被回收,但是它們又是正在使用的。
在上述四個操作中,最重要的是前三個操作。對于第二個操作調用到的MarkSweep類的成員函數RecursiveMarkDirtyObjects,我們前面已經分析過了。對于第三個操作,可以參考前面Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機GC過程對引用對象的處理,ART運行時和Dalvik虛擬機對引用對象的處理邏輯是一樣的。
接下來,我們主要分析第一個操作,即MarkSweep類的成員函數ReMarkRoots的實現,如下所示:
~~~
void MarkSweep::ReMarkRoots() {
timings_.StartSplit("ReMarkRoots");
Runtime::Current()->VisitRoots(ReMarkObjectVisitor, this, true, true);
timings_.EndSplit();
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數ReMarkRoots主要就是調用了我們前面分析過的Runtime類的成員函數VisitRoots來標記所有的根集對象,以便可以對它們直接和間接引用的對象進行遞歸標記。
理解了GC的Marking階段之后,再學習GC的Handle Dirty Objects階段就容易多了。接下來我們繼續分析GC的Reclaim階段。
6. ReclaimPhase
MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的Reclaim階段都是以MarkSweep類的成員函數ReclaimPhase為入口,它的實現如下所示:
~~~
void MarkSweep::ReclaimPhase() {
base::TimingLogger::ScopedSplit split("ReclaimPhase", &timings_);
Thread* self = Thread::Current();
if (!IsConcurrent()) {
ProcessReferences(self);
}
{
WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
SweepSystemWeaks();
}
if (IsConcurrent()) {
Runtime::Current()->AllowNewSystemWeaks();
base::TimingLogger::ScopedSplit split("UnMarkAllocStack", &timings_);
WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
accounting::ObjectStack* allocation_stack = GetHeap()->allocation_stack_.get();
// The allocation stack contains things allocated since the start of the GC. These may have been
// marked during this GC meaning they won't be eligible for reclaiming in the next sticky GC.
// Remove these objects from the mark bitmaps so that they will be eligible for sticky
// collection.
// There is a race here which is safely handled. Another thread such as the hprof could
// have flushed the alloc stack after we resumed the threads. This is safe however, since
// reseting the allocation stack zeros it out with madvise. This means that we will either
// read NULLs or attempt to unmark a newly allocated object which will not be marked in the
// first place.
mirror::Object** end = allocation_stack->End();
for (mirror::Object** it = allocation_stack->Begin(); it != end; ++it) {
const Object* obj = *it;
if (obj != NULL) {
UnMarkObjectNonNull(obj);
}
}
}
......
{
WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
// Reclaim unmarked objects.
Sweep(false);
// Swap the live and mark bitmaps for each space which we modified space. This is an
// optimization that enables us to not clear live bits inside of the sweep. Only swaps unbound
// bitmaps.
timings_.StartSplit("SwapBitmaps");
SwapBitmaps();
timings_.EndSplit();
// Unbind the live and mark bitmaps.
UnBindBitmaps();
}
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數ReclaimPhase首先判斷當前執行的GC是并行還是非并行的。對于并行GC,在前面的Handle Dirty Object階段,已經對引用對象作過處理了。但是對于非并行GC,由于不需要執行Handle Dirty Object階段,因此這時就要調用MarkSweep類的成員函數ProcessReferences對Soft Reference、Weak Reference、Phantom Reference和Finalizer Reference等引用對象進行處理。
MarkSweep類的成員函數ReclaimPhase接下來再調用另外一個成員函數SweepSystemWeaks清理那些沒有被標記的常量字符串、Monitor對象和在JNI創建的全局弱引用對象,它的實現如下所示:
~~~
void MarkSweep::SweepSystemWeaks() {
Runtime* runtime = Runtime::Current();
timings_.StartSplit("SweepSystemWeaks");
runtime->GetInternTable()->SweepInternTableWeaks(IsMarkedCallback, this);
runtime->GetMonitorList()->SweepMonitorList(IsMarkedCallback, this);
SweepJniWeakGlobals(IsMarkedCallback, this);
timings_.EndSplit();
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
回到MarkSweep類的成員函數ReclaimPhase中,接下來是針對并行GC進行處理。在前面的Handle Dirty Object階段,并行GC禁止了創建常量字符串、Monitor對象和JNI全局弱引用對象。現在由于對這三類對象已經處理完畢,因此就可以調用Runtime類的成員函數AllowNewSystemWeaks恢復可以創建了。
對于并行GC,還需要做的一個操作是,將Allocation Stack的對象對應的Mark Bitmap標記位清零。在并行GC的Marking階段,我們已經將Allocation Stack與Live Stack進行了交換,因此這時候保存在Allocation Stack的對象都是在并行GC執行的過程中分配的。這部分對象在并行GC執行的過程中,有可能會被標記。這意味著接下來交換Mark Bitmap和Live Bitmap時,這些保存在Allocation Stack的對象對應的Live Bitmap位是被設置為1的。這樣會導致下次執行Sticky Mark Sweep時,那些既在Allocation Stack中,但又不是根集對象也不是Dirty Card可達的對象不會被回收。因此,這里就需要調用成員函數UnMarkObjectNonNull將它們在Mark Bitmap中的標記位設置為0。
再接下來,MarkSweep類的成員函數ReclaimPhase就開始調用成員函數Sweep回收那些沒有被標記的對象,它的實現如下所示:
~~~
void MarkSweep::Sweep(bool swap_bitmaps) {
DCHECK(mark_stack_->IsEmpty());
base::TimingLogger::ScopedSplit("Sweep", &timings_);
const bool partial = (GetGcType() == kGcTypePartial);
SweepCallbackContext scc;
scc.mark_sweep = this;
scc.self = Thread::Current();
for (const auto& space : GetHeap()->GetContinuousSpaces()) {
// We always sweep always collect spaces.
bool sweep_space = (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyAlwaysCollect);
if (!partial && !sweep_space) {
// We sweep full collect spaces when the GC isn't a partial GC (ie its full).
sweep_space = (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect);
}
if (sweep_space) {
uintptr_t begin = reinterpret_cast<uintptr_t>(space->Begin());
uintptr_t end = reinterpret_cast<uintptr_t>(space->End());
scc.space = space->AsDlMallocSpace();
accounting::SpaceBitmap* live_bitmap = space->GetLiveBitmap();
accounting::SpaceBitmap* mark_bitmap = space->GetMarkBitmap();
if (swap_bitmaps) {
std::swap(live_bitmap, mark_bitmap);
}
if (!space->IsZygoteSpace()) {
base::TimingLogger::ScopedSplit split("SweepAllocSpace", &timings_);
// Bitmaps are pre-swapped for optimization which enables sweeping with the heap unlocked.
accounting::SpaceBitmap::SweepWalk(*live_bitmap, *mark_bitmap, begin, end,
&SweepCallback, reinterpret_cast<void*>(&scc));
} else {
base::TimingLogger::ScopedSplit split("SweepZygote", &timings_);
// Zygote sweep takes care of dirtying cards and clearing live bits, does not free actual
// memory.
accounting::SpaceBitmap::SweepWalk(*live_bitmap, *mark_bitmap, begin, end,
&ZygoteSweepCallback, reinterpret_cast<void*>(&scc));
}
}
}
SweepLargeObjects(swap_bitmaps);
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
參數swap_bitmaps表示在回收各個Space的垃圾時,是否需要交換它們的Mark Bitmap和Live Bitmap,這里傳進來的值等于false,表示不需要交換。交換的好處是可以在不鎖住堆的情況下執行垃圾回收階段。從前面分析的GarbageCollector類的成員函數Run可以知道,目 前ART運行堆的垃圾回收階段是在鎖住堆的前提下執行的,因此這里就不需要將參數swap_bitmaps設置為true。
MarkSweep類的成員函數Sweep首先回收Contiouous Space的垃圾,再回收Discontinous Space的垃圾,也就是Large Object Space的垃圾。這里首先要注意的一點是,只有Mark Sweep和Partial Mark Sweep會調用MarkSweep類的成員函數Sweep來清除垃圾,Sticky Mark Sweep會重寫父類Mark Sweep的成員函數Sweep,因為它需要清理的只是Allocation Stack的垃圾。因此,我們下面的討論都是Mark Sweep和Partial Mark Sweep的垃圾清理過程。
無論是Mark Sweep還是Partial Mark Sweep,回收策略為kGcRetentionPolicyAlwaysCollect的Space的垃圾都是需要回收的。但是對于Mark Sweep,回收策略為kGcRetentionPolicyFullCollect的Space的垃圾也是需要回收的。對于每一個Continous Space,如果通過上述邏輯判斷出它的垃圾是需要回收的,即變量sweep_space的值等于true,那么就需要調用SpaceBitmap類的成員函數SweepWalk進行回收。
我們知道,Image Space、Zygote Space和Allocation Space都是Continuous Space,它們的回收策略分別為kGcRetentionPolicyNeverCollect、kGcRetentionPolicyAlwaysCollect和kGcRetentionPolicyFullCollect,因此,這里需要進行垃圾回收的只有Zygote Space和Allocation Space。但是由于Zygote Space是用來在Zygote進程及其子進程共享的,因此,這里又不能對它進行真正的回收,否則的話,就會需要對這部內存進行寫操作,從而導致這部分內存不能夠再共享。這一點與前面Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機垃圾收集過程對Zygote堆的處理是一樣的。
因此,在調用SpaceBitmap類的成員函數SweepWalk對Zygote Space和Allocation Space的垃圾進行回收時,設置的回調函數是不一樣的。對于Allocation Space,設置的回調函數為MarkSweep類的靜態成員函數SweepCallback;而對于ZygoteSpace,設置的回調函數為MarkSweep類的靜態成員函數ZygoteSweepCallback。SpaceBitmap類的成員函數SweepWalk所做的事情就是找出那些上次GC之后是存活的,即在Live Bitmap中的標記位等于1,但是在當前GC時,在Mark Bitmap的標記位等于0,即沒有被標記的對象,然后再調用傳遞給它的回調函數對上述找到的對象進行真正清理操作。這一點與前面Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機垃圾清理邏輯也是一樣的。接下來我們就分別看看MarkSweep類的靜態成員函數SweepCallback和ZygoteSweepCallback的實現。
MarkSweep類的靜態成員函數SweepCallback的實現如下所示:
~~~
void MarkSweep::SweepCallback(size_t num_ptrs, Object** ptrs, void* arg) {
SweepCallbackContext* context = static_cast<SweepCallbackContext*>(arg);
MarkSweep* mark_sweep = context->mark_sweep;
Heap* heap = mark_sweep->GetHeap();
space::AllocSpace* space = context->space;
Thread* self = context->self;
Locks::heap_bitmap_lock_->AssertExclusiveHeld(self);
// Use a bulk free, that merges consecutive objects before freeing or free per object?
// Documentation suggests better free performance with merging, but this may be at the expensive
// of allocation.
size_t freed_objects = num_ptrs;
// AllocSpace::FreeList clears the value in ptrs, so perform after clearing the live bit
size_t freed_bytes = space->FreeList(self, num_ptrs, ptrs);
heap->RecordFree(freed_objects, freed_bytes);
mark_sweep->freed_objects_.fetch_add(freed_objects);
mark_sweep->freed_bytes_.fetch_add(freed_bytes);
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
參數ptrs指向一組需要回收的對象占用的內存塊的地址,而參數num_ptrs則表示這組內存塊的個數。第三個參數arg指向一個SweepCallbackContext對象,這個對象的成員變量space描述了參數ptrs指向的內存塊是所屬的Space。有了這些信息之后,就可以調用相應的Space的成員函數FreeList進行真正的垃圾回收了。我們知道,Allocation Space是使用DlMallocSpace來描述的,因此,這里就是調用DlMallocSpace類的成員函數FreeList來回收內存。
從前面ART運行時為新創建對象分配內存的過程分析一文可以知道,DlMallocSpace類是通過C庫提供的內存管理接口malloc來分配內存的,因此,它在回收內存時,也是使用對應的C庫內存管理接口free來回收內存。這一點與Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機的垃圾回收邏輯也是一樣的。
MarkSweep類的靜態成員函數SweepCallback最后還調用了Heap類的成員函數Record記錄了當前釋放的對象個數和內存字節數,以及更新Mark Sweep內部的釋放對象個數和內存字節數。
MarkSweep類的靜態成員函數ZygoteSweepCallback的實現如下所示:
~~~
void MarkSweep::ZygoteSweepCallback(size_t num_ptrs, Object** ptrs, void* arg) {
SweepCallbackContext* context = static_cast<SweepCallbackContext*>(arg);
Locks::heap_bitmap_lock_->AssertExclusiveHeld(context->self);
Heap* heap = context->mark_sweep->GetHeap();
// We don't free any actual memory to avoid dirtying the shared zygote pages.
for (size_t i = 0; i < num_ptrs; ++i) {
Object* obj = static_cast<Object*>(ptrs[i]);
heap->GetLiveBitmap()->Clear(obj);
heap->GetCardTable()->MarkCard(obj);
}
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
由于Zygote Space并不需要執行真正的垃圾清理工作,因此,MarkSweep類的靜態成員函數只是相應清理了垃圾對象在其Live Bitmap的標記位,以及將它在Card Table中對應的Card設置為DIRTY,使得下次GC時它們引用的其它對象也不會被回收。
MarkSweep類的成員函數Sweep中,回收完成Continous Space的垃圾之后,接下來再調用成員函數SweepLargeObjects回收Large Object Space的垃圾,它的實現如下所示:
~~~
void MarkSweep::SweepLargeObjects(bool swap_bitmaps) {
base::TimingLogger::ScopedSplit("SweepLargeObjects", &timings_);
// Sweep large objects
space::LargeObjectSpace* large_object_space = GetHeap()->GetLargeObjectsSpace();
accounting::SpaceSetMap* large_live_objects = large_object_space->GetLiveObjects();
accounting::SpaceSetMap* large_mark_objects = large_object_space->GetMarkObjects();
if (swap_bitmaps) {
std::swap(large_live_objects, large_mark_objects);
}
// O(n*log(n)) but hopefully there are not too many large objects.
size_t freed_objects = 0;
size_t freed_bytes = 0;
Thread* self = Thread::Current();
for (const Object* obj : large_live_objects->GetObjects()) {
if (!large_mark_objects->Test(obj)) {
freed_bytes += large_object_space->Free(self, const_cast<Object*>(obj));
++freed_objects;
}
}
freed_large_objects_.fetch_add(freed_objects);
freed_large_object_bytes_.fetch_add(freed_bytes);
GetHeap()->RecordFree(freed_objects, freed_bytes);
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
參數swap_bitmaps同樣是表示是否需要交換Large Object Space的Live Bitmap和Mark Bitmap,這里傳遞來的值等于false,表示不需要交換。
MarkSweep類的成員函數SweepLargeObjects通過遍歷Large Object Space的每一個對象,并且檢查它們在Mark Bitmap的標記位是否被設置了。如果沒有被設置,那么就調用Large Object Space的成員函數Free對其占用的內存塊進行釋放。垃圾清理完畢,MarkSweep類的成員函數SweepLargeObjects也會更新當前釋放的對象計數以及內存計數。
從前面ART運行時為新創建對象分配內存的過程分析一文可以知道,ART運行時使用的Large Object Space是一個LargeObjectMapSpace,它通過映射匿名共享內存為新創建對象分配內存,因此,LargeObjectMapSpace類的成員函數Free來釋放內存時,就相應地刪除對應的匿名共享內存就行了,如下所示:
~~~
size_t LargeObjectMapSpace::Free(Thread* self, mirror::Object* ptr) {
MutexLock mu(self, lock_);
MemMaps::iterator found = mem_maps_.find(ptr);
CHECK(found != mem_maps_.end()) << "Attempted to free large object which was not live";
DCHECK_GE(num_bytes_allocated_, found->second->Size());
size_t allocation_size = found->second->Size();
num_bytes_allocated_ -= allocation_size;
--num_objects_allocated_;
delete found->second;
mem_maps_.erase(found);
return allocation_size;
}
~~~
這個函數定義在文件art/runtime/gc/space/large_object_space.cc中。
LargeObjectMapSpace類之前分配的內存都是使用一個MemMap對象來描述,并且保存在成員變量mem_maps_描述的一個Map中,因此,LargeObjectMapSpace類的成員函數Free就先在這個Map里面找到與參數ptr對應的MemMap,然后將其從Map中移除。從Map中移除之后,對應的MemMap對象就會被析構,而MemMap對象被析構的時候,它內部維護的匿名共享內存就會被刪除了,如下所示:
~~~
MemMap::~MemMap() {
if (base_begin_ == NULL && base_size_ == 0) {
return;
}
int result = munmap(base_begin_, base_size_);
if (result == -1) {
PLOG(FATAL) << "munmap failed";
}
}
~~~
這個函數定義在文件art/runtime/mem_map.cc中。
從這里就可以看到,MemMap對象內部使用的匿名共享內存通過系統接口munmap進行刪除。
這樣,我們就分析完成各個Space的垃圾回收過程了,回到MarkSweep類的成員函數ReclaimPhase中,它最后還要做兩件事情。第一件事情是調用MarkSweep類的成員函數SwapBitmaps交換Live Bitmap和Mark Bitmap,使得Live Bitmap始終記錄的是上次GC之后存活的對象。第二件事情是調用MarkSweep類的成員函數UnBindBitmaps將Mark Bitmap清空,以便下次GC時可以使用。
至此,我們就分析完成Mark Sweep和Partial Mark Sweep的Reclaim階段了。前面提到,Sticky Mark Sweep重寫了父類MarkSweep的成員函數Sweep,以便可以只回收Allocation Stack的垃圾,它的實現如下所示:
~~~
void StickyMarkSweep::Sweep(bool swap_bitmaps) {
accounting::ObjectStack* live_stack = GetHeap()->GetLiveStack();
SweepArray(live_stack, false);
}
~~~
這個函數定義在文件art/runtime/gc/collector/sticky_mark_sweep.cc中。
注意,這里雖然獲得的是ART運行時的Live Stack,但是因為在前面的Marking階段中,Live Stack和Allocation Stack已經交換過了,因此這里獲得的實際上Allocation Stack。有了這個Allocation Stack之后,接下來就調用從父類MarkSweep繼承下來的成員函數SweepArray來處理它里面的垃圾對象。
MarkSweep類的成員函數SweepArray的實現如下所示:
~~~
void MarkSweep::SweepArray(accounting::ObjectStack* allocations, bool swap_bitmaps) {
space::DlMallocSpace* space = heap_->GetAllocSpace();
timings_.StartSplit("SweepArray");
// Newly allocated objects MUST be in the alloc space and those are the only objects which we are
// going to free.
accounting::SpaceBitmap* live_bitmap = space->GetLiveBitmap();
accounting::SpaceBitmap* mark_bitmap = space->GetMarkBitmap();
space::LargeObjectSpace* large_object_space = GetHeap()->GetLargeObjectsSpace();
accounting::SpaceSetMap* large_live_objects = large_object_space->GetLiveObjects();
accounting::SpaceSetMap* large_mark_objects = large_object_space->GetMarkObjects();
if (swap_bitmaps) {
std::swap(live_bitmap, mark_bitmap);
std::swap(large_live_objects, large_mark_objects);
}
size_t freed_bytes = 0;
size_t freed_large_object_bytes = 0;
size_t freed_objects = 0;
size_t freed_large_objects = 0;
size_t count = allocations->Size();
Object** objects = const_cast<Object**>(allocations->Begin());
Object** out = objects;
Object** objects_to_chunk_free = out;
// Empty the allocation stack.
Thread* self = Thread::Current();
for (size_t i = 0; i < count; ++i) {
Object* obj = objects[i];
// There should only be objects in the AllocSpace/LargeObjectSpace in the allocation stack.
if (LIKELY(mark_bitmap->HasAddress(obj))) {
if (!mark_bitmap->Test(obj)) {
// Don't bother un-marking since we clear the mark bitmap anyways.
*(out++) = obj;
// Free objects in chunks.
......
if (static_cast<size_t>(out - objects_to_chunk_free) == kSweepArrayChunkFreeSize) {
timings_.StartSplit("FreeList");
size_t chunk_freed_objects = out - objects_to_chunk_free;
freed_objects += chunk_freed_objects;
freed_bytes += space->FreeList(self, chunk_freed_objects, objects_to_chunk_free);
objects_to_chunk_free = out;
timings_.EndSplit();
}
}
} else if (!large_mark_objects->Test(obj)) {
++freed_large_objects;
freed_large_object_bytes += large_object_space->Free(self, obj);
}
}
// Free the remaining objects in chunks.
......
if (out - objects_to_chunk_free > 0) {
timings_.StartSplit("FreeList");
size_t chunk_freed_objects = out - objects_to_chunk_free;
freed_objects += chunk_freed_objects;
freed_bytes += space->FreeList(self, chunk_freed_objects, objects_to_chunk_free);
timings_.EndSplit();
}
......
timings_.EndSplit();
timings_.StartSplit("RecordFree");
......
freed_objects_.fetch_add(freed_objects);
freed_large_objects_.fetch_add(freed_large_objects);
freed_bytes_.fetch_add(freed_bytes);
freed_large_object_bytes_.fetch_add(freed_large_object_bytes);
timings_.EndSplit();
timings_.StartSplit("ResetStack");
allocations->Reset();
timings_.EndSplit();
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
保存在Allocation Stack中的對象要么是在Allocation Space上分配的,要么是Large Object Space分配的,因此MarkSweep類的成員函數SweepArray一開始就先取出這兩個Space的Mark Bitmap,然后再遍歷Allocation Stack的對象。對于Allocation Stack中每一個對象,首先判斷它在Allocation Space的Mark Bitmap中的標記位是否被設置了。如果沒有設置了,那么就說明這是一個需要回收的對象。如果在Allocation Space的Mark Bitmap中的標記位沒有被設置,再判斷它在Large Object Space的Mark Bitmap中的標記位是否被設置了。如果沒有被設置,那么也說明這是一個需要回收的對象。
對于屬于Allocation Space的垃圾對象,MarkSweep類的成員函數SweepArray通過調用DlMallocSpace類的成員函烽FreeList來批量回收,而對于屬于Large Object Space的垃圾對象,MarkSweep類的成員函數SweepArray則是通過LargeObjectMapSpace類的成員函數Free來逐個回收。
最后,MarkSweep類的成員函數SweepArray更新了當前釋放的對象個數和內存字節數,以及清空了Allocation Stack,以便下次可以繼續使用。
至此,Sticky Mark Sweep的Reclaim階段也分析完成了。接下來還剩下最后一個GC階段,即Finish階段。
7. FinishPhase
MarkSweep、PartialMarkSweep和StickyMarkSweep三個類的Reclaim階段都是以MarkSweep類的成員函數FinishPhase為入口,它的實現如下所示:
~~~
void MarkSweep::FinishPhase() {
base::TimingLogger::ScopedSplit split("FinishPhase", &timings_);
// Can't enqueue references if we hold the mutator lock.
Object* cleared_references = GetClearedReferences();
Heap* heap = GetHeap();
timings_.NewSplit("EnqueueClearedReferences");
heap->EnqueueClearedReferences(&cleared_references);
......
timings_.NewSplit("GrowForUtilization");
heap->GrowForUtilization(GetGcType(), GetDurationNs());
timings_.NewSplit("RequestHeapTrim");
heap->RequestHeapTrim();
// Update the cumulative statistics
total_time_ns_ += GetDurationNs();
total_paused_time_ns_ += std::accumulate(GetPauseTimes().begin(), GetPauseTimes().end(), 0,
std::plus<uint64_t>());
total_freed_objects_ += GetFreedObjects() + GetFreedLargeObjects();
total_freed_bytes_ += GetFreedBytes() + GetFreedLargeObjectBytes();
......
// Clear all of the spaces' mark bitmaps.
for (const auto& space : GetHeap()->GetContinuousSpaces()) {
if (space->GetGcRetentionPolicy() != space::kGcRetentionPolicyNeverCollect) {
space->GetMarkBitmap()->Clear();
}
}
mark_stack_->Reset();
// Reset the marked large objects.
space::LargeObjectSpace* large_objects = GetHeap()->GetLargeObjectsSpace();
large_objects->GetMarkObjects()->Clear();
}
~~~
這個函數定義在文件art/runtime/gc/collector/mark_sweep.cc中。
MarkSweep類的成員函數FinishPhase負責執行一些善后工作,包括:
* A. 調用Heap類的成員函數EnqueueClearedReferences將目標對象已經被回收了的引用對象添加到各自關聯的隊列中去,以便應用程序可以知道它們的目標對象已經被回收了。
* B. 調用Heap類的成員函數GrowForUtilization根據預先設置的堆目標利率以及最小和最大空閑內存數增長堆的大小。
* C. 調用Heap類的成員函數RequestHeapTrim對堆進行裁剪,以便可以將空閑內存臨時歸還給操作系統。
* D. 更新GC執行時間、暫停時間、釋放的對象個數和內存字節數等統計數據。
* E. 清空所有Space的Mark Bitmap和ART運行時的Mark Stack。
其中,A和B兩個工作和前面Dalvik虛擬機垃圾收集(GC)過程分析一文分析的Dalvik虛擬機垃圾收集的Finish階段做的工作是一樣的,因此這里不再詳細分析。此外,C這個工作會是通過向前面提到的在Java層創建的Heap Trimmer Daemon線程發送一個消息來完成的。Java層的Heap Trimmer Daemon線程收到這個通知之后,就會調用Heap類的成員函數Trim來執先裁剪堆的工作,如下所示:
~~~
size_t Heap::Trim() {
// Handle a requested heap trim on a thread outside of the main GC thread.
return alloc_space_->Trim();
}
~~~
這個函數定義在文件art/runtime/gc/heap.cc中。
從這里就可以看到,Heap類的成員函數Trim主要是裁剪Allocation Space的內存。
由于Heap類的成員變量alloc_space_指向的是一個DlMallocSpace對象,因此,接下來就調用DlMallocSpace類的成員函數Trim來裁剪內存,它的實現如下所示:
~~~
size_t DlMallocSpace::Trim() {
MutexLock mu(Thread::Current(), lock_);
// Trim to release memory at the end of the space.
mspace_trim(mspace_, 0);
// Visit space looking for page-sized holes to advise the kernel we don't need.
size_t reclaimed = 0;
mspace_inspect_all(mspace_, DlmallocMadviseCallback, &reclaimed);
return reclaimed;
}
~~~
這個函數定義在文件art/runtime/gc/space/dlmalloc_space.cc中。
從這里就可以看到,與前面Dalvik虛擬機Java堆創建過程分析一文,ART運行時也是通過C庫內存管理接口mspace_trim和mspace_inspect_all來裁剪內存的,因此這里就不再詳細分析了。
至此,我們就分析完成GC的Finish階段了,整個ART運行時的GC過程也分析完成了。從這個過程我們就可以看出,與前面Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃這個系列的文章分析的Dalvik虛擬機GC相比,ART運行時GC的優勢在于:
1. ART運行時堆的劃分和管理更細致,它分為Image Space、Zygote Space、Allocation Space和Large Object Space四個Space,再加上一個Allocation Stack。其中,Allocation Space和Large Object Space和Dalvik虛擬機的Zygote堆和Active堆作用是一樣的,而其余的Space則有特別的作用,例如,Image Space的對象是永遠不需要回收的。
2. ART運行時的每一個Space都有不同的回收策略,ART運行時根據這個特性提供了Mark Sweep、Partial Mark Sweep和Sticky Mark Sweep等三種回收力度不同的垃圾收集器。其中,Mark Sweep的垃圾回收力度最大,它會同時回收Zygote Space、Allocation Space和Large Object Space的垃圾,Partial Mark Sweep的垃圾回收力度居中,它只會同時回收Allocation Space和Large Object Space的垃圾,而Sticky Mark Sweep的垃圾回收力度最小,它只會回收Allocation Stack的垃圾,即上次GC以后分配出來的又不再使用了的對象。力度越大的垃圾收集器,回收垃圾時需要的時候也就越長。這樣我們就可以在應用程序運行的過程中根據不同的情景使用不同的垃圾收集器,那就可以更有效地執行垃圾回收過程。
3. ART運行時充分地利用了設備的CPU多核特性,在并行GC的執行過程中,將每一個并發階段的工作劃分成多個子任務,然后提交給一個線程池執行,這樣就可以更高效率地完成整個GC過程,避免長時間對應用程序造成停頓。
雖然ART運行時的GC更有優勢,但是它也實現得更加得復雜,而且很多方法也是借鑒了Dalvik虛擬機的GC機制的,因此,在學習ART運行時的GC機制之前,最好能夠先研究一下Dalvik虛擬機的GC機制,具體可以參考Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃這個系列的文章,而需要重新學習ART運行時的GC機制可以參考ART運行時垃圾收集機制簡要介紹和學習計劃這個系列的文章,
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析