通過前面一系列文章的學習,我們知道了ART運行時既支持Mark-Sweep GC,又支持Compacting GC。其中,Mark-Sweep GC執行效率更高,但是存在內存碎片問題;而Compacting GC執行效率較低,但是不存在內存碎片問題。ART運行時通過引入Foreground GC和Background GC的概念來對這兩種GC進行揚長避短。本文就詳細分析它們的執行過程以及切換過程。
在前面ART運行時Compacting GC簡要介紹和學習計劃和ART運行時Compacting GC堆創建過程分析這兩篇文章中,我們都有提到了ART運行時的Foreground GC和Background GC。它們是在ART運行時啓動通過-Xgc和-XX:BackgroundGC指定的。但是在某同一段時間,ART運行時只會執行Foreground GC或者Background GC。也就是說,Foreground GC和Background GC在整個應用程序的生命週期中是交替執行的。這就涉及到從Foreground GC切換到Background GC,或者從Background GC切換到Foreground GC的問題。
現在兩個問題就來了:什麼時候執行Foreground GC,什麼時候執行Background GC?什麼GC作爲Foreground GC最合適,什麼GC作爲Background GC最合適?
顧名思義,Foreground指的就是應用程序在前臺運行時,而Background就是應用程序在後臺運行時。因此,Foreground GC就是應用程序在前臺運行時執行的GC,而Background就是應用程序在後臺運行時執行的GC。
應用程序在前臺運行時,響應性是最重要的,因此也要求執行的GC是高效的。相反,應用程序在後臺運行時,響應性不是最重要的,這時候就適合用來解決堆的內存碎片問題。因此,Mark-Sweep GC適合作爲Foreground GC,而Compacting GC適合作爲Background GC。
但是,ART運行時又是怎麼知道應用程序目前是運行在前臺還是後臺呢?這就需要負責管理應用程序組件的系統服務ActivityManagerService閃亮登場了。因爲ActivityManagerService清楚地知道應用程序的每一個組件的運行狀態,也就是它們當前是在前臺運行還是後臺運行,從而得到應用程序是前臺運行還是後臺運行的結論。
我們通過圖1來描述應用程序的運行狀態與Foreground GC和Background GC的時序關係,如下所示:
圖1 應用程序運行狀態與Foreground GC和Background GC的時序關係
從圖1還可以看到,當從Foreground GC切換到Background GC,或者從Background GC切換到Foreground GC,會發生一次Compacting GC的行爲。這是由於Foreground GC和Background GC的底層堆空間結構是一樣的,因此發生Foreground GC和Background GC切換時,需要將當前存活的對象從一個Space轉移到另外一個Space上去。這個剛好就是Semi-Space GC和Generational Semi-Space GC合適乾的事情。
圖1中的顯示了應用程序的兩個狀態:kProcessStateJankPerceptible和kProcessStateJankImperceptible。其中,kProcessStateJankPerceptible說的就是應用程序處於用戶可感知的狀態,這就相當於是前臺狀態;而kProcessStateJankImperceptible說的就是應用程序處於用戶不可感知的狀態,這就相當於是後臺狀態。
接下來,我們就結合ActivityManagerService來分析Foreground GC和Background GC的切換過程。
從前面Android應用程序的Activity啓動過程簡要介紹和學習計劃這個系列的文章可以知道,應用程序組件是通過ActivityManagerService進行啓動的。例如,當我們從Launcher啓動一個應用程序時,實際的是在這個應用程序中Action和Category分別被配置爲MAIN和LAUNCHER的Activity。這個Activity最終由ActivityManagerService通知其所在的進程進行啓動工作的,也就是通過ApplicationThread類的成員函數scheduleLaunchActivity開始執行啓動工作的。其它類型的組件的啓動過程也是類似的,這裏我們僅以Activity的啓動過程作爲示例,來說明ART運行時如何知道要進行Foreground GC和Background GC切換的。
ApplicationThread類的成員函數scheduleLaunchActivity的實現如下所示:
- public final class ActivityThread {
- ......
- private class ApplicationThread extends ApplicationThreadNative {
- ......
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) {
- updateProcessState(procState, false);
- ActivityClientRecord r = new ActivityClientRecord();
- r.token = token;
- r.ident = ident;
- r.intent = intent;
- r.voiceInteractor = voiceInteractor;
- r.activityInfo = info;
- r.compatInfo = compatInfo;
- r.state = state;
- r.persistentState = persistentState;
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
- r.startsNotResumed = notResumed;
- r.isForward = isForward;
- r.profilerInfo = profilerInfo;
- updatePendingConfiguration(curConfig);
- sendMessage(H.LAUNCH_ACTIVITY, r);
- }
- ......
- }
- ......
- }
ApplicationThread類的成員函數scheduleLaunchActivity首先是調用另外一個成員函數updateProcessState更新進程的當前狀態,接着再將其餘參數封裝在一個ActivityClientRecord對象中,並且將這個ActivityClientRecord對象通過一個H.LAUNCH_ACTIVITY消息傳遞給應用程序主線程處理。應用程序主線程處理對這個消息的處理就是啓動指定的Activity,這個過程可以參考前面Android應用程序的Activity啓動過程簡要介紹和學習計劃這個系列的文章。ApplicationThread類的成員函數scheduleLaunchActivity還調用了另外一個成員函數updatePendingConfiguration將參數curConfig描述的系統當前配置信息保存下來待後面處理。
我們主要關注ApplicationThread類的成員函數updateProcessState,因爲它涉及到進程狀態的更新,它的實現如下所示:
- public final class ActivityThread {
- ......
- private class ApplicationThread extends ApplicationThreadNative {
- ......
- public void updateProcessState(int processState, boolean fromIpc) {
- synchronized (this) {
- if (mLastProcessState != processState) {
- mLastProcessState = processState;
- // Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.
- final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
- final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
- int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;
- // TODO: Tune this since things like gmail sync are important background but not jank perceptible.
- if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;
- }
- VMRuntime.getRuntime().updateProcessState(dalvikProcessState);
- ......
- }
- }
- }
- ......
- }
- ......
- }
ApplicationThread類的成員變量mLastProcessState描述的是進程上一次的狀態,而參數processState描述的是進程當前的狀態。當這兩者的值不一致時,就表明進程的狀態發生了變化,這時候就需要調用VMRuntime類的成員函數updateProcessState通知ART運行時,以便ART運行時可以在Foreground GC和Background GC之間切換。
ActivityManagerService一共定義了14種進程狀態,如下所示:
- public class ActivityManager {
- ......
- /** @hide Process is a persistent system process. */
- public static final int PROCESS_STATE_PERSISTENT = 0;
- /** @hide Process is a persistent system process and is doing UI. */
- public static final int PROCESS_STATE_PERSISTENT_UI = 1;
- /** @hide Process is hosting the current top activities. Note that this covers
- * all activities that are visible to the user. */
- public static final int PROCESS_STATE_TOP = 2;
- /** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3;
- /** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4;
- /** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 5;
- /** @hide Process is in the background, but it can't restore its state so we want
- * to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 6;
- /** @hide Process is in the background running a service. Unlike oom_adj, this level
- * is used for both the normal running in background state and the executing
- * operations state. */
- public static final int PROCESS_STATE_SERVICE = 7;
- /** @hide Process is in the background running a receiver. Note that from the
- * perspective of oom_adj receivers run at a higher foreground level, but for our
- * prioritization here that is not necessary and putting them below services means
- * many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 8;
- /** @hide Process is in the background but hosts the home activity. */
- public static final int PROCESS_STATE_HOME = 9;
- /** @hide Process is in the background but hosts the last shown activity. */
- public static final int PROCESS_STATE_LAST_ACTIVITY = 10;
- /** @hide Process is being cached for later use and contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY = 11;
- /** @hide Process is being cached for later use and is a client of another cached
- * process that contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12;
- /** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 13;
- ......
- }
每一個進程狀態都通過一個整數來描述,其中,值越小就表示進程越重要。ART運行時將狀態值大於等於PROCESS_STATE_IMPORTANT_FOREGROUND的進程都認爲是用戶可感知的,也就是前臺進程,其餘的進程則認爲是用戶不可感知的,也就是後臺進程。通過這種方式,ApplicationThread類的成員函數updateProcessState就可以簡化ART運行時對進程狀態的處理。
除了上述的Activity的Launch啓動生命週期函數被ActivityManagerService通知調用時,Activity的Resume生命週期函數被ActivityManagerService通知調用調用時,也會發生類似的通過VMRuntime類的成員函數updateProcessState通知ART運行時應用程序狀態發生了改變。對於其它的組件,例如Broadcast Receiver組件被觸發時,Service組件被創建以及被綁定時,也會通過VMRuntime類的成員函數updateProcessState通知ART運行時應用程序狀態發生了改變。
不過,上述組件的生命週期對應的都是應用程序處於前臺時的情況,也就是要求ART運行時從Background GC切換爲Foreground GC的情況。當應用程序處於後臺時,ActivityManagerService是通過直接設置應用程序的狀態來通知ART運行時應用程序狀態發生了改變的。
ApplicationThread類實現了一個Binder接口setProcessState,供ActivityManagerService直接設置應用程序的狀態,它的實現如下所示:
- public final class ActivityThread {
- ......
- private class ApplicationThread extends ApplicationThreadNative {
- ......
- public void setProcessState(int state) {
- updateProcessState(state, true);
- }
- ......
- }
- ......
- }
ApplicationThread類實現的Binder接口setProcessState也是通過上面分析的成員函數updateProcessState來通知ART運行時進程狀態發生了改變的。不過這時候進程的狀態就有可能是從前面進程變爲後臺進程,例如當運行在該進程的Activity組件處理Stop狀態時。
接下來我們繼續分析VMRuntime類的成員函數updateProcessState的實現,以便了解ART運行時執行Foreground GC和Background GC切換的過程,如下所示:
- public final class VMRuntime {
- ......
- /**
- * Let the heap know of the new process state. This can change allocation and garbage collection
- * behavior regarding trimming and compaction.
- */
- public native void updateProcessState(int state);
- ......
- }
VMRuntime類的成員函數updateProcessState是一個Native函數,它由C++層的函數VMRuntime_updateProcessState實現,如下所示:
- static void VMRuntime_updateProcessState(JNIEnv* env, jobject, jint process_state) {
- Runtime::Current()->GetHeap()->UpdateProcessState(static_cast<gc::ProcessState>(process_state));
- ......
- }
函數VMRuntime_updateProcessState主要是調用了Heap類的成員函數UpdateProcessState來通知ART運行時切換Foreground GC和Background GC,後者的實現如下所示:
- void Heap::UpdateProcessState(ProcessState process_state) {
- if (process_state_ != process_state) {
- process_state_ = process_state;
- ......
- if (process_state_ == kProcessStateJankPerceptible) {
- // Transition back to foreground right away to prevent jank.
- RequestCollectorTransition(foreground_collector_type_, 0);
- } else {
- // Don't delay for debug builds since we may want to stress test the GC.
- // If background_collector_type_ is kCollectorTypeHomogeneousSpaceCompact then we have
- // special handling which does a homogenous space compaction once but then doesn't transition
- // the collector.
- RequestCollectorTransition(background_collector_type_,
- kIsDebugBuild ? 0 : kCollectorTransitionWait);
- }
- }
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
Heap類的成員變量process_state_記錄了進程上一次的狀態,參數process_state描述進程當前的狀態。當這兩者的值不相等的時候,就說明進程狀態發生了變化。
如果是從kProcessStateJankImperceptible狀態變爲kProcessStateJankPerceptible狀態,那麼就調用Heap類的成員函數RequestCollectorTransition請求馬上將當前的GC設置爲Foreground GC。
如果是從kProcessStateJankPerceptible狀態變爲kProcessStateJankImperceptible,那麼就調用Heap類的成員函數RequestCollectorTransition請求將當前的GC設置爲Background GC。注意,在這種情況下,對於非DEBUG版本的ART運行時,不是馬上將當前的GC設置爲Background GC的,而是指定在kCollectorTransitionWait(5秒)時間後再設置。這樣使得進程進入後臺運行的一小段時間內,仍然可以使用效率較高的Mark-Sweep GC。
Heap類的成員函數RequestCollectorTransition的實現如下所示:
- void Heap::RequestCollectorTransition(CollectorType desired_collector_type, uint64_t delta_time) {
- Thread* self = Thread::Current();
- {
- MutexLock mu(self, *heap_trim_request_lock_);
- if (desired_collector_type_ == desired_collector_type) {
- return;
- }
- heap_transition_or_trim_target_time_ =
- std::max(heap_transition_or_trim_target_time_, NanoTime() + delta_time);
- desired_collector_type_ = desired_collector_type;
- }
- SignalHeapTrimDaemon(self);
- }
Heap類的成員函數RequestCollectorTransition首先將要切換至的目標GC以及時間點記錄在成員變量desired_collector_type_和heap_transition_or_trim_target_time_中,接着再調用另外一個成員函數SignalHeapTrimDaemon喚醒一個Heap Trimmer守護線程來執行GC切換操作。注意,如果上一次請求的GC切換還未執行,又請求了下一次GC切換,並且下一次GC切換指定的時間大於上一次指定的時間,那麼上次請求的GC切換就會被取消。
Heap類的成員函數RequestCollectorTransition的實現如下所示:
- void Heap::SignalHeapTrimDaemon(Thread* self) {
- JNIEnv* env = self->GetJniEnv();
- DCHECK(WellKnownClasses::java_lang_Daemons != nullptr);
- DCHECK(WellKnownClasses::java_lang_Daemons_requestHeapTrim != nullptr);
- env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
- WellKnownClasses::java_lang_Daemons_requestHeapTrim);
- CHECK(!env->ExceptionCheck());
- }
Heap類的成員函數RequestCollectorTransition通過JNI接口調用了Daemons類的靜態成員函數requestHeapTrim請求執行一次GC切換操作。
Daemons類的靜態成員函數requestHeapTrim的實現如下所示:
- public final class Daemons {
- ......
- public static void requestHeapTrim() {
- synchronized (HeapTrimmerDaemon.INSTANCE) {
- HeapTrimmerDaemon.INSTANCE.notify();
- }
- }
- ......
- }
這個函數定義在文件libcore/libart/src/main/Java/java/lang/Daemons.java中。
在前面ART運行時垃圾收集(GC)過程分析一文中提到,Java層的java.lang.Daemons類在加載的時候,會啓動五個與堆或者GC相關的守護線程,其中一個守護線程就是HeapTrimmerDaemon,這裏通過調用它的成員函數notify來喚醒它。
HeapTrimmerDaemon原先被Blocked在成員函數run中,當它被喚醒之後 ,就會繼續執行它的成員函數run,如下所示:
- public final class Daemons {
- ......
- private static class HeapTrimmerDaemon extends Daemon {
- private static final HeapTrimmerDaemon INSTANCE = new HeapTrimmerDaemon();
- @Override public void run() {
- while (isRunning()) {
- try {
- synchronized (this) {
- wait();
- }
- VMRuntime.getRuntime().trimHeap();
- } catch (InterruptedException ignored) {
- }
- }
- }
- }
- ......
- }
這個函數定義在文件libcore/libart/src/main/java/java/lang/Daemons.java中。
從這裏就可以看到,HeapTrimmerDaemon被喚醒之後,就會調用VMRuntime類的成員函數trimHeap來執行GC切換操作。
VMRuntime類的成員函數trimHeap是一個Native函數,由C++層的函數VMRuntime_trimHeap實現,如下所示:
- static void VMRuntime_trimHeap(JNIEnv*, jobject) {
- Runtime::Current()->GetHeap()->DoPendingTransitionOrTrim();
- }
函數VMRuntime_trimHeap又是通過調用Heap類的成員函數DoPendingTransitionOrTrim來執行GC切換操作的,如下所示:
- void Heap::DoPendingTransitionOrTrim() {
- Thread* self = Thread::Current();
- CollectorType desired_collector_type;
- // Wait until we reach the desired transition time.
- while (true) {
- uint64_t wait_time;
- {
- MutexLock mu(self, *heap_trim_request_lock_);
- desired_collector_type = desired_collector_type_;
- uint64_t current_time = NanoTime();
- if (current_time >= heap_transition_or_trim_target_time_) {
- break;
- }
- wait_time = heap_transition_or_trim_target_time_ - current_time;
- }
- ScopedThreadStateChange tsc(self, kSleeping);
- usleep(wait_time / 1000); // Usleep takes microseconds.
- }
- // Launch homogeneous space compaction if it is desired.
- if (desired_collector_type == kCollectorTypeHomogeneousSpaceCompact) {
- if (!CareAboutPauseTimes()) {
- PerformHomogeneousSpaceCompact();
- }
- // No need to Trim(). Homogeneous space compaction may free more virtual and physical memory.
- desired_collector_type = collector_type_;
- return;
- }
- // Transition the collector if the desired collector type is not the same as the current
- // collector type.
- TransitionCollector(desired_collector_type);
- ......
- // Do a heap trim if it is needed.
- Trim();
- }
這個函數定義在文件art/runtime/gc/heap.cc中。
前面提到,下一次GC切換時間記錄在Heap類的成員變量heap_transition_or_trim_target_time_中,因此,Heap類的成員函數DoPendingTransitionOrTrim首先是看看當前時間是否已經達到指定的GC切換時間。如果還沒有達到,那麼就進行等待,直到時間到達爲止。
有一種特殊情況,如果要切換至的GC是kCollectorTypeHomogeneousSpaceCompact,並且Heap類的成員函數CareAboutPauseTimes表明不在乎執行HomogeneousSpaceCompact GC帶來的暫停時間,那麼就會調用Heap類的成員函數PerformHomogeneousSpaceCompact執行一次同構空間壓縮。Heap類的成員函數PerformHomogeneousSpaceCompact執行同構空間壓縮的過程,可以參考前面ART運行時Compacting GC爲新創建對象分配內存的過程分析一文。
Heap類的成員函數CareAboutPauseTimes實際上是判斷進程的當前狀態是否是用戶可感知的,即是否等於kProcessStateJankPerceptible。如果是的話,就說明它在乎GC執行時帶來的暫停時間。它的實現如下所示:
- class Heap {
- public:
- ......
- // Returns true if we currently care about pause times.
- bool CareAboutPauseTimes() const {
- return process_state_ == kProcessStateJankPerceptible;
- }
- ......
- };
回到Heap類的成員函數DoPendingTransitionOrTrim中,我們繼續討論要切換至的GC是kCollectorTypeHomogeneousSpaceCompact的情況。如果Heap類的成員函數CareAboutPauseTimes表明在乎執行HomogeneousSpaceCompact GC帶來的暫停時間,那麼就不會調用Heap類的成員函數PerformHomogeneousSpaceCompact執行同構空間壓縮。
只要切換至的GC是kCollectorTypeHomogeneousSpaceCompact,無論上述的哪一種情況,都不會真正執行GC切換的操作,因此這時候Heap類的成員函數DoPendingTransitionOrTrim就可以返回了。
從前面的調用過程可以知道,要切換至的GC要麼是Foreground GC,要麼是Background GC。一般來說,我們是不會將Foreground GC設置爲HomogeneousSpaceCompact GC的,但是卻有可能將Background GC設置爲HomogeneousSpaceCompact GC。因此,上述討論的情況只發生在Foreground GC切換爲Background GC的時候。
另一方面,如果要切換至的GC不是kCollectorTypeHomogeneousSpaceCompact,那麼Heap類的成員函數DoPendingTransitionOrTrim就會調用另外一個成員函數TransitionCollector執行切換GC操作。一旦GC切換完畢,Heap類的成員函數DoPendingTransitionOrTrim還會調用成員函數Trim對當前ART運行時堆進行裁剪,也就是將現在沒有使用到的內存歸還給內核。這個過程可以參考前面ART運行時垃圾收集(GC)過程分析一文。
接下來我們繼續分析Heap類的成員函數TransitionCollector的實現,以便了解GC的切換過程,如下所示:
- void Heap::TransitionCollector(CollectorType collector_type) {
- if (collector_type == collector_type_) {
- return;
- }
- ......
- ThreadList* const tl = runtime->GetThreadList();
- ......
- // Busy wait until we can GC (StartGC can fail if we have a non-zero
- // compacting_gc_disable_count_, this should rarely occurs).
- for (;;) {
- {
- ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
- MutexLock mu(self, *gc_complete_lock_);
- // Ensure there is only one GC at a time.
- WaitForGcToCompleteLocked(kGcCauseCollectorTransition, self);
- // Currently we only need a heap transition if we switch from a moving collector to a
- // non-moving one, or visa versa.
- const bool copying_transition = IsMovingGc(collector_type_) != IsMovingGc(collector_type);
- // If someone else beat us to it and changed the collector before we could, exit.
- // This is safe to do before the suspend all since we set the collector_type_running_ before
- // we exit the loop. If another thread attempts to do the heap transition before we exit,
- // then it would get blocked on WaitForGcToCompleteLocked.
- if (collector_type == collector_type_) {
- return;
- }
- // GC can be disabled if someone has a used GetPrimitiveArrayCritical but not yet released.
- if (!copying_transition || disable_moving_gc_count_ == 0) {
- // TODO: Not hard code in semi-space collector?
- collector_type_running_ = copying_transition ? kCollectorTypeSS : collector_type;
- break;
- }
- }
- usleep(1000);
- }
- tl->SuspendAll();
- switch (collector_type) {
- case kCollectorTypeSS: {
- if (!IsMovingGc(collector_type_)) {
- // Create the bump pointer space from the backup space.
- ......
- std::unique_ptr<MemMap> mem_map(main_space_backup_->ReleaseMemMap());
- // We are transitioning from non moving GC -> moving GC, since we copied from the bump
- // pointer space last transition it will be protected.
- .....
- mem_map->Protect(PROT_READ | PROT_WRITE);
- bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space",
- mem_map.release());
- AddSpace(bump_pointer_space_);
- Compact(bump_pointer_space_, main_space_, kGcCauseCollectorTransition);
- // Use the now empty main space mem map for the bump pointer temp space.
- mem_map.reset(main_space_->ReleaseMemMap());
- // Unset the pointers just in case.
- if (dlmalloc_space_ == main_space_) {
- dlmalloc_space_ = nullptr;
- } else if (rosalloc_space_ == main_space_) {
- rosalloc_space_ = nullptr;
- }
- // Remove the main space so that we don't try to trim it, this doens't work for debug
- // builds since RosAlloc attempts to read the magic number from a protected page.
- RemoveSpace(main_space_);
- RemoveRememberedSet(main_space_);
- delete main_space_; // Delete the space since it has been removed.
- main_space_ = nullptr;
- RemoveRememberedSet(main_space_backup_.get());
- main_space_backup_.reset(nullptr); // Deletes the space.
- temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
- mem_map.release());
- AddSpace(temp_space_);
- }
- break;
- }
- case kCollectorTypeMS:
- // Fall through.
- case kCollectorTypeCMS: {
- if (IsMovingGc(collector_type_)) {
- ......
- std::unique_ptr<MemMap> mem_map(temp_space_->ReleaseMemMap());
- RemoveSpace(temp_space_);
- temp_space_ = nullptr;
- mem_map->Protect(PROT_READ | PROT_WRITE);
- CreateMainMallocSpace(mem_map.get(), kDefaultInitialSize, mem_map->Size(),
- mem_map->Size());
- mem_map.release();
- // Compact to the main space from the bump pointer space, don't need to swap semispaces.
- AddSpace(main_space_);
- Compact(main_space_, bump_pointer_space_, kGcCauseCollectorTransition);
- mem_map.reset(bump_pointer_space_->ReleaseMemMap());
- RemoveSpace(bump_pointer_space_);
- bump_pointer_space_ = nullptr;
- const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
- ......
- main_space_backup_.reset(CreateMallocSpaceFromMemMap(mem_map.get(), kDefaultInitialSize,
- mem_map->Size(), mem_map->Size(),
- name, true));
- ......
- mem_map.release();
- }
- break;
- }
- default: {
- ......
- break;
- }
- ChangeCollector(collector_type);
- tl->ResumeAll();
- ......
- }
Heap類的成員函數TransitionCollector首先判斷ART運行時當前使用的GC與要切換至的GC是一樣的,那麼就什麼也不用做就返回了。
另一方面,如果ART運行時當前使用的GC與要切換至的GC是不一樣的,那麼接下來就要將ART運行時當前使用的GC切換至參數collector_type描述的GC了。由於將GC切換是通過執行一次Semi-Space GC或者Generational Semi-Space GC來實現的,因此Heap類的成員函數TransitionCollector在繼續往下執行之前,要先調用Heap類的成員函數WaitForGcToCompleteLocked判斷當前是否有GC正在執行。如果有的話,就進行等待,直到對應的GC執行完爲止。
注意,有可能當前正在執行的GC就是要切換至的GC,在這種情況下,就沒有必要將當前使用的GC切換爲參數collector_type描述的GC了。此外,只有從當前執行的GC和要切換至的GC不同時爲Compacting GC或者Mark-Sweep GC的時候,Heap類的成員函數TransitionCollector纔會真正執行切換的操作。換句話說,只有從Compacting GC切換爲Mark-Sweep GC或者從Mark-Sweep GC切換爲Compacting GC時,Heap類的成員函數TransitionCollector纔會真正執行切換的操作。但是,如果這時候ART運行時被禁止執行Compacting GC,即Heap類的成員函數disable_moving_gc_count_不等於0,那麼Heap類的成員函數TransitionCollector就需要繼續等待,直到ART運行時重新允許執行Compacting GC爲止。這是因爲接下來的GC切換操作是通過執行一次Compacting GC來實現的。
接下來的GC切換操作是通過調用Heap類的成員函數Compact來實現的。關於Heap類的成員函數Compact,我們在前面ART運行時Compacting GC爲新創建對象分配內存的過程分析一文已經分析過了,它主要通過執行一次Semi-Space GC、Generational Semi-Space GC或者Mark-Compact GC將指定的Source Space上的存活對象移動至指定的Target Space中。如果Source Space與Target Space相同,那麼執行的就是Mark-Compact GC,否則就是Semi-Space GC或者Generational Semi-Space GC。由於Heap類的成員函數Compact是需要在Stop-the-world的前提下執行的,因此在調用它的前後,需要執行掛起和恢復除當前正在執行的線程之外的所有ART運行時線程。
Heap類的成員函數TransitionCollector通過switch語句來確定需要傳遞給成員函數Compact的Source Space和Target Space。通過這個switch語句,我們也可以更清楚看到Heap類的成員函數TransitionCollector允許從什麼GC切換至什麼GC。
首先,可切換至的GC只有三種,分別爲Semi-Space GC、Mark-Sweep GC和Concurrent Mark-Sweep GC。其中,當要切換至的GC爲Mark-Sweep GC和Concurrent Mark-Sweep GC時,它們的切換過程是一樣的。因此,接下來我們就分兩種情況來討論。
第一種情況是要切換至的GC爲Semi-Space GC。根據我們前面的分析,這時候原來的GC只能爲Mark-Sweep GC或者Concurrent Mark-Sweep GC。否則的話,就不需要執行GC切換操作。從前面ART運行時Compacting GC堆創建過程分析一文可以知道,當原來的GC爲Mark-Sweep GC或者Concurrent Mark-Sweep GC時,ART運行時堆由Image Space、Zygote Space、Non Moving Space、Main Space、Main Backup Space和Large Object Space組成。這時候要做的是將Main Space上的存活對象移動至一個新創建的Bump Pointer Space上去。也就是說,這時候的Source Space爲Main Space,而Target Space爲Bump Pointer Space。
Main Space就保存在Heap類的成員變量main_space_中,因此就很容易可以獲得。但是這時候是沒有現成的Bump Pointer Space的,因此就需要創建一個。由於這時候的Main Backup Space是閒置的,並且當GC切換完畢,它也用不上了,因此我們就可以將Main Backup Space底層使用的內存塊獲取回來,然後再封裝成一個Bump Pointer Space。注意,這時候創建的Bump Pointer Space也是作爲GC切換完成後的Semi-Space GC的From Space使用的,因此,除了要將它保存在Heap類的成員變量bump_pointer_space_之外,還要將它添加到ART運行時堆的Space列表中去。
這時候Source Space和Target Space均已準備完畢,因此就可以執行Heap類的成員函數Compact了。執行完畢,還需要做一系列的清理工作,包括:
1. 刪除Main Space及其關聯的Remembered Set。從前面ART運行時Compacting GC堆創建過程分析一文可以知道,Heap類的成員變量dlmalloc_space_和rosalloc_space_指向的都是Main Space。既然現在Main Space要被刪除了,因此就需要將它們設置爲nullptr。
2. 刪除Main Backup Space及其關聯的Remembered Set。
3. 創建一個Bump Pointer Space保存在Heap類的成員變量temp_space_中,作爲GC切換完成後的Semi-Space GC的To Space使用。注意,這個To Space底層使用的內存塊是來自於原來的Main Space的。
這意味着將從Mark-Sweep GC或者Concurrent Mark-Sweep GC切換爲Semi-Space GC之後,原來的Main Space和Main Backup Space就消失了,並且多了兩個Bump Pointer Space,其中一個作爲From Space,另外一個作爲To Space,並且From Space上的對象來自於原來的Main Space的存活對象。
第二種情況是要切換至Mark-Sweep GC或者Concurrent Mark-Sweep GC。根據我們前面的分析,這時候原來的GC只能爲Semi-Space GC、Generational Semi-Space GC或者Mark-Compact GC。否則的話,就不需要執行GC切換操作。從前面ART運行時Compacting GC堆創建過程分析一文可以知道,當原來的GC爲Semi-Space GC、Generational Semi-Space GC或者Mark-Compact GC時,ART運行時堆由Image Space、Zygote Space、Non Moving Space、Bump Pointer Space、Temp Space和Large Object Space組成。這時候要做的是將Bump Pointer Space上的存活對象移動至一個新創建的Main Space上去。也就是說,這時候的Source Space爲Bump Pointer Space,而Target Space爲Main Space。
Bump Pointer Space就保存在Heap類的成員變量bump_pointer_space_中,因此就很容易可以獲得。但是這時候是沒有現成的Main Space的,因此就需要創建一個。由於這時候的Temp Space是閒置的,並且當GC切換完畢,它也用不上了,因此我們就可以將Temp Space底層使用的內存塊獲取回來,然後再封裝成一個Main Space,這是通過調用Heap類的成員函數CreateMainMallocSpace來實現的。注意,Heap類的成員函數CreateMainMallocSpace在執行的過程中,會將創建的Main Space保存在Heap類的成員變量main_space_中,並且它也是作爲GC切換完成後的Mark-Sweep GC或者Concurrent Mark-Sweep GC的Main Space使用的,因此,就還要將它添加到ART運行時堆的Space列表中去。
這時候Source Space和Target Space均已準備完畢,因此就可以執行Heap類的成員函數Compact了。執行完畢,還需要做一系列的清理工作,包括:
1. 刪除Bump Pointer Space。
2. 刪除Temp Space。
3. 創建一個Main Backup Space,保存在Heap類的成員變量main_space_backup_中,這是通過調用Heap類的成員函數CreateMallocSpaceFromMemMap實現的,並且該Main Backup Space底層使用的內存塊是來自於原來的Bump Pointer Space的。
這樣,GC切換的操作就基本執行完畢,最後還需要做的一件事情是調用Heap類的成員函數ChangeCollector記錄當前使用的GC,以及相應地調整當前可用的內存分配器。這個函數的具體實現可以參考前面ART運行時Compacting GC爲新創建對象分配內存的過程分析一文。