ART運行時Foreground GC和Background GC切換過程分析

      通過前面一系列文章的學習,我們知道了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的實現如下所示:

[java] view plain copy
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     private class ApplicationThread extends ApplicationThreadNative {  
  5.         ......  
  6.   
  7.         public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,  
  8.                 ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,  
  9.                 IVoiceInteractor voiceInteractor, int procState, Bundle state,  
  10.                 PersistableBundle persistentState, List<ResultInfo> pendingResults,  
  11.                 List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,  
  12.                 ProfilerInfo profilerInfo) {  
  13.   
  14.             updateProcessState(procState, false);  
  15.   
  16.             ActivityClientRecord r = new ActivityClientRecord();  
  17.   
  18.             r.token = token;  
  19.             r.ident = ident;  
  20.             r.intent = intent;  
  21.             r.voiceInteractor = voiceInteractor;  
  22.             r.activityInfo = info;  
  23.             r.compatInfo = compatInfo;  
  24.             r.state = state;  
  25.             r.persistentState = persistentState;  
  26.   
  27.             r.pendingResults = pendingResults;  
  28.             r.pendingIntents = pendingNewIntents;  
  29.   
  30.             r.startsNotResumed = notResumed;  
  31.             r.isForward = isForward;  
  32.   
  33.             r.profilerInfo = profilerInfo;  
  34.   
  35.             updatePendingConfiguration(curConfig);  
  36.   
  37.             sendMessage(H.LAUNCH_ACTIVITY, r);  
  38.         }  
  39.   
  40.         ......  
  41.     }  
  42.   
  43.     ......  
  44. }  

       這個函數定義在文件frameworks/base/core/java/android/app/ActivityThread.java中。

       ApplicationThread類的成員函數scheduleLaunchActivity首先是調用另外一個成員函數updateProcessState更新進程的當前狀態,接着再將其餘參數封裝在一個ActivityClientRecord對象中,並且將這個ActivityClientRecord對象通過一個H.LAUNCH_ACTIVITY消息傳遞給應用程序主線程處理。應用程序主線程處理對這個消息的處理就是啓動指定的Activity,這個過程可以參考前面Android應用程序的Activity啓動過程簡要介紹和學習計劃這個系列的文章。ApplicationThread類的成員函數scheduleLaunchActivity還調用了另外一個成員函數updatePendingConfiguration將參數curConfig描述的系統當前配置信息保存下來待後面處理。

       我們主要關注ApplicationThread類的成員函數updateProcessState,因爲它涉及到進程狀態的更新,它的實現如下所示:

[java] view plain copy
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     private class ApplicationThread extends ApplicationThreadNative {  
  5.         ......  
  6.   
  7.         public void updateProcessState(int processState, boolean fromIpc) {  
  8.             synchronized (this) {  
  9.                 if (mLastProcessState != processState) {  
  10.                     mLastProcessState = processState;  
  11.                     // Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.  
  12.                     final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;  
  13.                     final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;  
  14.                     int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;  
  15.                     // TODO: Tune this since things like gmail sync are important background but not jank perceptible.  
  16.                     if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {  
  17.                         dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;  
  18.                     }  
  19.                     VMRuntime.getRuntime().updateProcessState(dalvikProcessState);  
  20.                     ......  
  21.                 }  
  22.             }  
  23.         }  
  24.   
  25.         ......  
  26.     }  
  27.   
  28.     ......  
  29. }  
       這個函數定義在文件frameworks/base/core/java/android/app/ActivityThread.java中。

       ApplicationThread類的成員變量mLastProcessState描述的是進程上一次的狀態,而參數processState描述的是進程當前的狀態。當這兩者的值不一致時,就表明進程的狀態發生了變化,這時候就需要調用VMRuntime類的成員函數updateProcessState通知ART運行時,以便ART運行時可以在Foreground GC和Background GC之間切換。

       ActivityManagerService一共定義了14種進程狀態,如下所示:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class ActivityManager {  
  2.     ......  
  3.   
  4.     /** @hide Process is a persistent system process. */  
  5.     public static final int PROCESS_STATE_PERSISTENT = 0;  
  6.   
  7.     /** @hide Process is a persistent system process and is doing UI. */  
  8.     public static final int PROCESS_STATE_PERSISTENT_UI = 1;  
  9.   
  10.     /** @hide Process is hosting the current top activities.  Note that this covers 
  11.      * all activities that are visible to the user. */  
  12.     public static final int PROCESS_STATE_TOP = 2;  
  13.   
  14.     /** @hide Process is important to the user, and something they are aware of. */  
  15.     public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3;  
  16.   
  17.     /** @hide Process is important to the user, but not something they are aware of. */  
  18.     public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4;  
  19.   
  20.     /** @hide Process is in the background running a backup/restore operation. */  
  21.     public static final int PROCESS_STATE_BACKUP = 5;  
  22.   
  23.     /** @hide Process is in the background, but it can't restore its state so we want 
  24.      * to try to avoid killing it. */  
  25.     public static final int PROCESS_STATE_HEAVY_WEIGHT = 6;  
  26.   
  27.     /** @hide Process is in the background running a service.  Unlike oom_adj, this level 
  28.      * is used for both the normal running in background state and the executing 
  29.      * operations state. */  
  30.     public static final int PROCESS_STATE_SERVICE = 7;  
  31.   
  32.     /** @hide Process is in the background running a receiver.   Note that from the 
  33.      * perspective of oom_adj receivers run at a higher foreground level, but for our 
  34.      * prioritization here that is not necessary and putting them below services means 
  35.      * many fewer changes in some process states as they receive broadcasts. */  
  36.     public static final int PROCESS_STATE_RECEIVER = 8;  
  37.   
  38.     /** @hide Process is in the background but hosts the home activity. */  
  39.     public static final int PROCESS_STATE_HOME = 9;  
  40.   
  41.     /** @hide Process is in the background but hosts the last shown activity. */  
  42.     public static final int PROCESS_STATE_LAST_ACTIVITY = 10;  
  43.   
  44.     /** @hide Process is being cached for later use and contains activities. */  
  45.     public static final int PROCESS_STATE_CACHED_ACTIVITY = 11;  
  46.   
  47.     /** @hide Process is being cached for later use and is a client of another cached 
  48.      * process that contains activities. */  
  49.     public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12;  
  50.   
  51.     /** @hide Process is being cached for later use and is empty. */  
  52.     public static final int PROCESS_STATE_CACHED_EMPTY = 13;  
  53.   
  54.     ......  
  55. }  
       這些進程狀態值定義在文件frameworks/base/core/java/android/app/ActivityManager.java。

       每一個進程狀態都通過一個整數來描述,其中,值越小就表示進程越重要。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直接設置應用程序的狀態,它的實現如下所示:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     private class ApplicationThread extends ApplicationThreadNative {  
  5.         ......  
  6.   
  7.         public void setProcessState(int state) {  
  8.             updateProcessState(state, true);  
  9.         }  
  10.   
  11.         ......  
  12.     }  
  13.   
  14.     ......  
  15. }  
       這個函數定義在文件frameworks/base/core/java/android/app/ActivityThread.java中。

       ApplicationThread類實現的Binder接口setProcessState也是通過上面分析的成員函數updateProcessState來通知ART運行時進程狀態發生了改變的。不過這時候進程的狀態就有可能是從前面進程變爲後臺進程,例如當運行在該進程的Activity組件處理Stop狀態時。

       接下來我們繼續分析VMRuntime類的成員函數updateProcessState的實現,以便了解ART運行時執行Foreground GC和Background GC切換的過程,如下所示:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public final class VMRuntime {  
  2.     ......  
  3.   
  4.     /** 
  5.      * Let the heap know of the new process state. This can change allocation and garbage collection 
  6.      * behavior regarding trimming and compaction. 
  7.      */  
  8.     public native void updateProcessState(int state);  
  9.   
  10.     ......  
  11. }  
       這個函數定義在文件libcore/libart/src/main/java/dalvik/system/VMRuntime.java中。

       VMRuntime類的成員函數updateProcessState是一個Native函數,它由C++層的函數VMRuntime_updateProcessState實現,如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. static void VMRuntime_updateProcessState(JNIEnv* env, jobject, jint process_state) {  
  2.   Runtime::Current()->GetHeap()->UpdateProcessState(static_cast<gc::ProcessState>(process_state));  
  3.   ......  
  4. }  
       這個函數定義在文件art/runtime/native/dalvik_system_VMRuntime.cc中。

       函數VMRuntime_updateProcessState主要是調用了Heap類的成員函數UpdateProcessState來通知ART運行時切換Foreground GC和Background GC,後者的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. void Heap::UpdateProcessState(ProcessState process_state) {  
  2.   if (process_state_ != process_state) {  
  3.     process_state_ = process_state;  
  4.     ......  
  5.     if (process_state_ == kProcessStateJankPerceptible) {  
  6.       // Transition back to foreground right away to prevent jank.  
  7.       RequestCollectorTransition(foreground_collector_type_, 0);  
  8.     } else {  
  9.       // Don't delay for debug builds since we may want to stress test the GC.  
  10.       // If background_collector_type_ is kCollectorTypeHomogeneousSpaceCompact then we have  
  11.       // special handling which does a homogenous space compaction once but then doesn't transition  
  12.       // the collector.  
  13.       RequestCollectorTransition(background_collector_type_,  
  14.                                  kIsDebugBuild ? 0 : kCollectorTransitionWait);  
  15.     }  
  16.   }  
  17. }  

       這個函數定義在文件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的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. void Heap::RequestCollectorTransition(CollectorType desired_collector_type, uint64_t delta_time) {  
  2.   Thread* self = Thread::Current();  
  3.   {  
  4.     MutexLock mu(self, *heap_trim_request_lock_);  
  5.     if (desired_collector_type_ == desired_collector_type) {  
  6.       return;  
  7.     }  
  8.     heap_transition_or_trim_target_time_ =  
  9.         std::max(heap_transition_or_trim_target_time_, NanoTime() + delta_time);  
  10.     desired_collector_type_ = desired_collector_type;  
  11.   }  
  12.   SignalHeapTrimDaemon(self);  
  13. }  
       這個函數定義在文件art/runtime/gc/heap.cc中。

       Heap類的成員函數RequestCollectorTransition首先將要切換至的目標GC以及時間點記錄在成員變量desired_collector_type_和heap_transition_or_trim_target_time_中,接着再調用另外一個成員函數SignalHeapTrimDaemon喚醒一個Heap Trimmer守護線程來執行GC切換操作。注意,如果上一次請求的GC切換還未執行,又請求了下一次GC切換,並且下一次GC切換指定的時間大於上一次指定的時間,那麼上次請求的GC切換就會被取消。

       Heap類的成員函數RequestCollectorTransition的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. void Heap::SignalHeapTrimDaemon(Thread* self) {  
  2.   JNIEnv* env = self->GetJniEnv();  
  3.   DCHECK(WellKnownClasses::java_lang_Daemons != nullptr);  
  4.   DCHECK(WellKnownClasses::java_lang_Daemons_requestHeapTrim != nullptr);  
  5.   env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,  
  6.                             WellKnownClasses::java_lang_Daemons_requestHeapTrim);  
  7.   CHECK(!env->ExceptionCheck());  
  8. }  
       這個函數定義在文件art/runtime/gc/heap.cc中。

       Heap類的成員函數RequestCollectorTransition通過JNI接口調用了Daemons類的靜態成員函數requestHeapTrim請求執行一次GC切換操作。

       Daemons類的靜態成員函數requestHeapTrim的實現如下所示:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public final class Daemons {  
  2.     ......  
  3.   
  4.     public static void requestHeapTrim() {  
  5.         synchronized (HeapTrimmerDaemon.INSTANCE) {  
  6.             HeapTrimmerDaemon.INSTANCE.notify();  
  7.         }  
  8.     }  
  9.   
  10.     ......  
  11. }  

       這個函數定義在文件libcore/libart/src/main/Java/java/lang/Daemons.java中。

       在前面ART運行時垃圾收集(GC)過程分析一文中提到,Java層的java.lang.Daemons類在加載的時候,會啓動五個與堆或者GC相關的守護線程,其中一個守護線程就是HeapTrimmerDaemon,這裏通過調用它的成員函數notify來喚醒它。

       HeapTrimmerDaemon原先被Blocked在成員函數run中,當它被喚醒之後 ,就會繼續執行它的成員函數run,如下所示:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public final class Daemons {  
  2.     ......  
  3.   
  4.     private static class HeapTrimmerDaemon extends Daemon {  
  5.         private static final HeapTrimmerDaemon INSTANCE = new HeapTrimmerDaemon();  
  6.   
  7.         @Override public void run() {  
  8.             while (isRunning()) {  
  9.                 try {  
  10.                     synchronized (this) {  
  11.                         wait();  
  12.                     }  
  13.                     VMRuntime.getRuntime().trimHeap();  
  14.                 } catch (InterruptedException ignored) {  
  15.                 }  
  16.             }  
  17.         }  
  18.     }  
  19.   
  20.     ......  
  21. }  

       這個函數定義在文件libcore/libart/src/main/java/java/lang/Daemons.java中。

       從這裏就可以看到,HeapTrimmerDaemon被喚醒之後,就會調用VMRuntime類的成員函數trimHeap來執行GC切換操作。

       VMRuntime類的成員函數trimHeap是一個Native函數,由C++層的函數VMRuntime_trimHeap實現,如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. static void VMRuntime_trimHeap(JNIEnv*, jobject) {  
  2.   Runtime::Current()->GetHeap()->DoPendingTransitionOrTrim();  
  3. }  
       這個函數定義在文件art/runtime/native/dalvik_system_VMRuntime.cc 。

       函數VMRuntime_trimHeap又是通過調用Heap類的成員函數DoPendingTransitionOrTrim來執行GC切換操作的,如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. void Heap::DoPendingTransitionOrTrim() {  
  2.   Thread* self = Thread::Current();  
  3.   CollectorType desired_collector_type;  
  4.   // Wait until we reach the desired transition time.  
  5.   while (true) {  
  6.     uint64_t wait_time;  
  7.     {  
  8.       MutexLock mu(self, *heap_trim_request_lock_);  
  9.       desired_collector_type = desired_collector_type_;  
  10.       uint64_t current_time = NanoTime();  
  11.       if (current_time >= heap_transition_or_trim_target_time_) {  
  12.         break;  
  13.       }  
  14.       wait_time = heap_transition_or_trim_target_time_ - current_time;  
  15.     }  
  16.     ScopedThreadStateChange tsc(self, kSleeping);  
  17.     usleep(wait_time / 1000);  // Usleep takes microseconds.  
  18.   }  
  19.   // Launch homogeneous space compaction if it is desired.  
  20.   if (desired_collector_type == kCollectorTypeHomogeneousSpaceCompact) {  
  21.     if (!CareAboutPauseTimes()) {  
  22.       PerformHomogeneousSpaceCompact();  
  23.     }  
  24.     // No need to Trim(). Homogeneous space compaction may free more virtual and physical memory.  
  25.     desired_collector_type = collector_type_;  
  26.     return;  
  27.   }  
  28.   // Transition the collector if the desired collector type is not the same as the current  
  29.   // collector type.  
  30.   TransitionCollector(desired_collector_type);  
  31.   ......  
  32.   // Do a heap trim if it is needed.  
  33.   Trim();  
  34. }  

       這個函數定義在文件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執行時帶來的暫停時間。它的實現如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. class Heap {  
  2.  public:  
  3.   ......  
  4.   
  5.   // Returns true if we currently care about pause times.  
  6.   bool CareAboutPauseTimes() const {  
  7.     return process_state_ == kProcessStateJankPerceptible;  
  8.   }  
  9.   
  10.  ......  
  11. };  
       這個函數定義在文件art/runtime/gc/heap.h中。

       回到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的切換過程,如下所示:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. void Heap::TransitionCollector(CollectorType collector_type) {  
  2.   if (collector_type == collector_type_) {  
  3.     return;  
  4.   }  
  5.   ......  
  6.   ThreadList* const tl = runtime->GetThreadList();  
  7.   ......  
  8.   // Busy wait until we can GC (StartGC can fail if we have a non-zero  
  9.   // compacting_gc_disable_count_, this should rarely occurs).  
  10.   for (;;) {  
  11.     {  
  12.       ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);  
  13.       MutexLock mu(self, *gc_complete_lock_);  
  14.       // Ensure there is only one GC at a time.  
  15.       WaitForGcToCompleteLocked(kGcCauseCollectorTransition, self);  
  16.       // Currently we only need a heap transition if we switch from a moving collector to a  
  17.       // non-moving one, or visa versa.  
  18.       const bool copying_transition = IsMovingGc(collector_type_) != IsMovingGc(collector_type);  
  19.       // If someone else beat us to it and changed the collector before we could, exit.  
  20.       // This is safe to do before the suspend all since we set the collector_type_running_ before  
  21.       // we exit the loop. If another thread attempts to do the heap transition before we exit,  
  22.       // then it would get blocked on WaitForGcToCompleteLocked.  
  23.       if (collector_type == collector_type_) {  
  24.         return;  
  25.       }  
  26.       // GC can be disabled if someone has a used GetPrimitiveArrayCritical but not yet released.  
  27.       if (!copying_transition || disable_moving_gc_count_ == 0) {  
  28.         // TODO: Not hard code in semi-space collector?  
  29.         collector_type_running_ = copying_transition ? kCollectorTypeSS : collector_type;  
  30.         break;  
  31.       }  
  32.     }  
  33.     usleep(1000);  
  34.   }  
  35.   tl->SuspendAll();  
  36.   switch (collector_type) {  
  37.     case kCollectorTypeSS: {  
  38.       if (!IsMovingGc(collector_type_)) {  
  39.         // Create the bump pointer space from the backup space.  
  40.         ......  
  41.         std::unique_ptr<MemMap> mem_map(main_space_backup_->ReleaseMemMap());  
  42.         // We are transitioning from non moving GC -> moving GC, since we copied from the bump  
  43.         // pointer space last transition it will be protected.  
  44.         .....  
  45.         mem_map->Protect(PROT_READ | PROT_WRITE);  
  46.         bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space",  
  47.                                                                         mem_map.release());  
  48.         AddSpace(bump_pointer_space_);  
  49.         Compact(bump_pointer_space_, main_space_, kGcCauseCollectorTransition);  
  50.         // Use the now empty main space mem map for the bump pointer temp space.  
  51.         mem_map.reset(main_space_->ReleaseMemMap());  
  52.         // Unset the pointers just in case.  
  53.         if (dlmalloc_space_ == main_space_) {  
  54.           dlmalloc_space_ = nullptr;  
  55.         } else if (rosalloc_space_ == main_space_) {  
  56.           rosalloc_space_ = nullptr;  
  57.         }  
  58.         // Remove the main space so that we don't try to trim it, this doens't work for debug  
  59.         // builds since RosAlloc attempts to read the magic number from a protected page.  
  60.         RemoveSpace(main_space_);  
  61.         RemoveRememberedSet(main_space_);  
  62.         delete main_space_;  // Delete the space since it has been removed.  
  63.         main_space_ = nullptr;  
  64.         RemoveRememberedSet(main_space_backup_.get());  
  65.         main_space_backup_.reset(nullptr);  // Deletes the space.  
  66.         temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",  
  67.                                                                 mem_map.release());  
  68.         AddSpace(temp_space_);  
  69.       }  
  70.       break;  
  71.     }  
  72.     case kCollectorTypeMS:  
  73.       // Fall through.  
  74.     case kCollectorTypeCMS: {  
  75.       if (IsMovingGc(collector_type_)) {  
  76.         ......  
  77.         std::unique_ptr<MemMap> mem_map(temp_space_->ReleaseMemMap());  
  78.         RemoveSpace(temp_space_);  
  79.         temp_space_ = nullptr;  
  80.         mem_map->Protect(PROT_READ | PROT_WRITE);  
  81.         CreateMainMallocSpace(mem_map.get(), kDefaultInitialSize, mem_map->Size(),  
  82.                               mem_map->Size());  
  83.         mem_map.release();  
  84.         // Compact to the main space from the bump pointer space, don't need to swap semispaces.  
  85.         AddSpace(main_space_);  
  86.         Compact(main_space_, bump_pointer_space_, kGcCauseCollectorTransition);  
  87.         mem_map.reset(bump_pointer_space_->ReleaseMemMap());  
  88.         RemoveSpace(bump_pointer_space_);  
  89.         bump_pointer_space_ = nullptr;  
  90.         const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];  
  91.         ......  
  92.         main_space_backup_.reset(CreateMallocSpaceFromMemMap(mem_map.get(), kDefaultInitialSize,  
  93.                                                              mem_map->Size(), mem_map->Size(),  
  94.                                                              name, true));  
  95.         ......  
  96.         mem_map.release();  
  97.       }  
  98.       break;  
  99.     }  
  100.     default: {  
  101.       ......  
  102.       break;  
  103.     }  
  104.   ChangeCollector(collector_type);  
  105.   tl->ResumeAll();  
  106.   ......  
  107. }  
       這個函數定義在文件art/runtime/gc/heap.h中。

       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爲新創建對象分配內存的過程分析一文。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章