说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

ART运行时Foreground GC和Background GC切换过程分析

HTML文档下载 WORD文档下载 PDF文档下载
ART运行时支持Mark-Sweep GC和Compacting GC。前者执行效率更高,但存在内存碎片问题;后者则与之相反。ART运行时通过引入Foreground GC和Background GC来对这两种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的实现如下所示:

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);          }            ......      }        ......  }  

这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java中。

ApplicationThread类的成员函数scheduleLaunchActivity首先是调用另外一个成员函数updateProcessState更新进程的当前状态,接着再将其余参数封装在一个ActivityClientRecord对象中,并且将这个ActivityClientRecord对象通过一个H.LAUNCH_ACTIVITY消息传递给应用程序主线程处理。应用程序主线程处理对这个消息的处理就是启动指定的Activity,这个过程可以参考前面Android应用程序的Activity启动过程简要介绍和学习计划这个系列的文章。ApplicationThread类的成员函数scheduleLaunchActivity还调用了另外一个成员函数updatePendingConfiguration将参数cureConfig描述的系统当前配置信息保存下来待后面处理。

我们主要关注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);                      ......                  }              }          }            ......      }        ......  }  

这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java中。

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;        ......  }  

这些进程状态值定义在文件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直接设置应用程序的状态,它的实现如下所示:

public final class ActivityThread {      ......        private class ApplicationThread extends ApplicationThreadNative {          ......            public void setProcessState(int state) {              updateProcessState(state, true);          }            ......      }        ......  }  

这个函数定义在文件frameworks/base/core/java/android/app/ActivityThread.java中。

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);        ......  }  

这个函数定义在文件libcore/libart/src/main/java/dalvik/system/VMRuntime.java中。

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));    ......  }  

这个函数定义在文件art/runtime/native/dalvik_system_VMRuntime.cc中。

函数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类的成员变量prcess_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);  }  

这个函数定义在文件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的实现如下所示:

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());  }  

这个函数定义在文件art/runtime/gc/heap.cc中。

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原先被Block在成员函数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();  }  

这个函数定义在文件art/runtime/native/dalvik_system_VMRuntime.cc 。

函数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;    }   ......  };  

这个函数定义在文件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的切换过程,如下所示:

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();    ......  }  

这个函数定义在文件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为新创建对象分配内存的过程分析一文。

至此,ART运行时Foreground GC和Background GC的切换过程分析就分析完成了,ART运行时引进的Compacting GC的学习计划也完成了,重新学习可以参考ART运行时Compacting GC简要介绍和学习计划一文。

本文出自罗升阳《老罗的Android之旅》专栏


作者简介:

@罗升阳,CSDN Android开发专栏博客《老罗的Android之旅》作者,发表了一百多篇高质量的Android系统原创性文章,亦是《Android系统源代码情景分析》一书的作者,在Android系统源代码领域有着很深的见解。


CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面,如果您有想分享的技术、观点,可通过电子邮件(tangxy#csdn.net,请把#改成@)投稿。

第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。


2012年12月份全球主流浏览器市场份额排行榜 冲榜不一定要刷榜:增加游戏曝光度的7大杀手锏 科技vs政治:FTC对Google的反垄断调查结束背后 威胁百度:奇虎360确认将与谷歌达成合作关系 Facebook更新Messenger应用 推免费网络电话服务 网上疯传iOS7概念设计:Dashboard是真是假? 过时的UI设计 Ubuntu手机系统前途不被看好 三星将于今年发布Tizen设备 将对苹果形成威胁? 分享9条经典的编程语录 国际著名黑客大赛介绍与比较 微软Azure CDN服务全球范围内出现宕机 双向多功能U盘:从Android设备到PC还有多远? 证明了自己:微软Win8获美国防部6.17亿订单 2013 Facebook黑客杯报名即将开始 解决Win8下IE10无法打开的故障 [简讯] Scala 2.10.0发布! 收购传闻:Apple一厢情愿还是Waze待价而沽 回顾过去,展望2013:移动开发引擎、工具和语言盘点 成本为0!Netflix开源工具Janitor Monkey清理AWS iOS开发者讲述在Google工作的三个月 GitHub历史上最糟糕宕机事故回放及反省 疑因竞争 谷歌有意让地图与WP手机不兼容 移动游戏开者必看:海外VC最关心什么? 淘宝UED:随侃设计师的个人素养 TIOBE 2013年1月:不负重望 Objective-C再次赢得桂冠! 大数据:商业或技术的挑战? 开源移动统计:Cobub Razor近期版本大升级 无人机应用,为何屡遭苹果拒绝 2013年Java继续火的五大理由 Rails所有版本都有SQL注入漏洞?其实没那么严重 如何设置Windows Server 2012 NTFS权限 紧急求救:在VC++里如何通过ATL OLE DB访问(INSERT\UPDATE\DELETE\SELECT)ORACLE和SQL SERVER里的BLOB字段内容? 高薪寻找西安的兼职UNIX程序 员 在delphi中,怎么调用api函数? www.alldas.de为什么无法访问了??? 如何检测被覆盖的窗口区域是否发生了变化? 高薪寻找西安的兼职UNIX 程序员 如何制作下拉菜单 请高手指点:好猫为何不工作? 一种设想,20台电脑5根电话线上网,如何达到较快的速度? 对话框程序调试时的问题?!!!! 请介绍几本好用的自学C语言的书籍好吗?? 紧急求救,请高手来看看 .NET和JAVA到底哪个好? 谁来救我?ISequentialStream::write()为何不好使? 定义类型的困惑 大虾们,如何做一个图标选择框!!!(详情见内) ADO编程问题求解!!!!!!!!!!! ****************简单问题,高分求救??????????????????????? 请问怎样在TabStrip中的选项卡中增加图片?谢了 工程如何能建的工整一些? www.alldas.de为什么无法访问了? 关于动态控件的问题 ADO编程问题求解 怎么编程产生1MHz甚至更高频率的周期信号? 紧急求助—有关JMail4.1附件问题 挥泪大送分!!!!! 怎样才能让自己变胖? 做个仅有对话框的应用,但想把它放到托盘, 给ASP爱好者提个建议! 关于SQLserver2000“视图“的两个问题,请教高手! 好久没上网了,送分以庆祝再回csdn!!!!! 做浏览器,如何实现 “后退”,“前进”,这二个功能? 如何使C:盘,D盘的根目录不共享? 用resin时,是否需要jdk,如何配置?分必给! SDI中的某一个子视图里怎样对主窗口的状态栏进行更改?? ◇◆◇ 失恋代表什么? ◇◆◇ 我英语不懂,想考高程.行吗 ◇◆◇ 学生生涯快完了,大家来说说对学生生涯的感受,特别是什么最深刻的! ◇◆◇ 求救!如何把SQL命令从前端应用程序传给应用程序服务器 要去招商行笔试了,有参加过的兄弟给点建议。 ###############讨论一下->微软全力推出.NET技术和C#语言的出现,VC++还有多久的寿命??? 要去招商行笔试了,有参加过的兄弟给点建议 如何发一消息,帮别人问的 高手帮助!!! 要去招商行笔试,有参加过的兄弟给点建议。 要去招商行笔试,有参加过的兄弟给点建议 我配置的IIS,为什么只能读数据库,不能写入?为什么? 我要去招商行笔试了,有参加过的兄弟给点建议。 好久没发帖了……………… 请问LINUX/UNIX下那种扩展名的文件是可执行的?DOS的。EXE对应的格式是什么? C++入门看什么书好? 方程2的x次方=3-2x的解为x1,方程log2(x)=3-2x的解为x2,求x1+x2的值答案是3/2,可是我怎么都是2啊? 已知2+根号3是方程x2-4x+c的一个根,求方程的另一个根及c的值 无论x取何值,多项式(m-1)x的3次方+2mx的2次方+(m+1)x+p都等于px的2次方-qx+p,求(m+p)的p-q的值? 设方程2的-x次方=lgx绝对值的两个根为x1,x2,则x1乘以x2的积与0,1的大小关系 已知关于X的一元二次方程x的平方+(m+3)+m+1=0.若X1,X2是原方程的根,且|X1-X2|=二倍根号二.求m的值 当x等于3时,代数式px的3次方+qx+1的值为2002,当x等于-3时,求代数式px的3次方+qx+1的值 关于x的二次方程2x^2+(log2为底,m为真)x+log2为底根号m为真=0有两个实数根,求m的值两个相等的实数根。。对不起啊,少打两个字。。 x1、x2是方程x^+根号p*x+q=0的两个根且x1^+x1x2+x2^=3/2,1/x1^+1/x2^=5/2,求p、q的值 设x1、x2是关于x的方程x²+px+q=0的两根,x1+1、x2+1是关于x的x²+qx+p=0的两根,求p、q的值 若关于x的方程(根号x+3)+2x+m=0只有一个实数根,求m的取值范围 方程根号4-x2=lgx的根个数是几个? 已知X²+PX+q=O的两根之差等于方程X²+qX+P=0的两根之差那么除去P²-4q>0与q²-4P>0之外,P与q还应具有关系式 已知关于方程x^2+2x+2根号(x^2+2x+2p) -p^2=0,其中p为实数(1)若方程没有实数根,求p的范围(2)若p>0,为p为何值时,方程只有一个实数根,并求出这个根用换元法设根号中为a,我想知道分解因式后 已知X1,X2为方程X的平方加px加q等于0的两根,且X1加X2等于6,X1的平方加X2的平方等于20求p和q的值? 关于x的一元二次方程x²+px+q=0的两根分别为x1=-3 x2=1,求p和q的值? log根号2^(x-5)log2^(x-1) 已知关于X的方程x^2-px+q=0的两根分别为x1,x2,且x1^2+x2^2=7,1/x1+1/x2=3,求p+q的值 如果关于x的一元二次方程x的平方+px+q=0的两根分别为x1=2,x2=1,那么p、q的值分别是A 、3,2 B、3,-2 C、2,-3 D、2,3应该选哪个我想知道过程是如何解 log2(1+根号2+根号3)+log2(1+根号2 -根号3) 已知关于x的方程x²-px+q=0两根为x1、x2,则-x²+px-q=0等于多少A、-(x+x1)(x+x2)B、(x+x1)(x-x2)C、-(x-x1)(x-x2)C、(x-x1)(x+x1)选哪一个?最好有过程 已知、x1=2+3i是实系数一元二次方程x²+px+q=0的一个根求实数p,q及另一个跟x2 已知函数f(x)=2^x+x,g(x)=x-log1/2x(1/2为底数),h(x)=log2x-根号x(2为底数)的零点是x1,x2,x3,比较大小 已知三角形ABC的一边长为5,另外两边长恰是方程2x^2-12x+m+1=0的两根,求实数m的取值范围已知方程x^2+px+q=0的两个实数根分别比方程x^2+qx+p=0的两实数根小1,求以1/p,1/q为两根的一元二次方程 如果关于x的一元二次方程x2+px+q=0的两根分别为x1=3、x2=1,那么这个一元二次方程是( )A.x2+3x+4=0 B.x2-4x+3=0 C.x2+4x-3=0 D.x2+3x-4=0 已知关于x的方程3x^2-6(m-1)x+m^2+1=0的两个根x1,x2满足|x1|+|x2|=2,求实数m的值 已知x∈[根号2,8],函数f(x)=log2 (x/2)乘以log根号2 ((根号x)/2)求该函数的最大值与最小值,注:这两个对数式是相乘,底数分别是2和(根号2),真数分别是 (x÷2)与 (根号x÷2),在10月23日下午3 已知关于x的一元二次方程x2+px+q=0的两个实数根为p.q,则p,q=? 设x1,x2是方程3x方-5x-7=0的两根.求(1)x1方+x2方=?(2)1/x1+1/x2=?(3)X2/X1+X1/X2=? 已知函数f(x)=log2(2^x+1),g(x)=log2(2^x-1),若关于x的函数F(x)=g(x)-f(x)-m在[1,2]上有零点,求m范围 已知方程x^2+px+q=0的两根是a,b.求证:一元二次方程qx^2+p(1+q)x+(1+q)^2=0的根为a+1/b和b+1/a如题. 设方程3x²-5x-7=0的两根,分别为x1,x2,求一.(x1-x2) 二.(x1-2)(x2-2) 若关于x的方程根号下1-x^2=log2(x-a)有正数解,则实数a的取值范围 设x1,x2是方程x²+px+q=0的两实数,x1+1,x2+1是关于x的方程x²+qx+p=0的两实跟,则p=?q=? 已知,方程3x²-5x-7=0的两个根为x1、x2(韦达定理)求|x1-x2|根据韦达定理x1+x2=-b/ax1x2=c/a 关于X的方程根号下1-X^2=x+a在区间[1,1]上有解,则实数a的取值范围是 若p,q为正实数,且关于x的方程x2+px+q=0与x2+qx+p=0均有实根,求p+q的最小值cdinten 你的答案不对恶,把4代进去就是错的。 已知方程-x2+3x-m=3-x在x∈(0,3)内有唯一解,求实数m的取值范围 使得方程根号(16-x^2)-x-m=0有实数解,则实数m的取值范围?答案是4≤m≤4根号2.这个好像要画图!麻烦给个图解, 方程x的平方+px+q=0的解集是A,方程X的平方+qx+2p=0的解集是B,又AnB={-1},求AUB 若正数a为方程3x^2-5x+3=0的两个根x1,x2的比例中项,求a(我觉得3x^2-5x+3=0这个方程无解) 已知全集U=R A={x f(x)=根号(X-1)(X-2)} B={x log2(X-a) 若方程x^2+px+q=0与x^2+qx+p=0有一公共根,且p不等于q,求(p+q)^2009的值答出来还送积分哦~加油.. 设集合A={x1,x2,x3}={x|x^3=1,x∈C},则集合B={x1x2,x1x3,x2x3}(x1,x2,x3∈A)与A的关系是 已知函数f(x)=log2(x^2-ax-a)在区间(-∞ ,1减根号3]是单调递减函数.求实数a的取值范围 一元二次方程px平方+qx平方+r=0(p不等于0)的两根为0和-1,则q:p= 已知5a的y+5次方b的3x次方,与-4a的2x次方b的2-4y次方是同类项,则x= y= 已知a>0a≠1,试求使方程log2(x-ka)=log2根号下(x^2-a^2)有解的k的取值范围希望得到图象法的解释,谢 已知方程x^2+px+q=0的一个根与方程x^2+qx-p=0的一个根互为相反数,并且p不等于q,求p-q的值. 已知2a的y+5次方b的3x次方与2分之5a的2x次方b的2-4y次方是同类项则x=【】,y=【】 若log2(2-a)/根号(a-1)有意义,则a的取值范围 已知:方程x^2+px+q=0的两个根为a,b,而a+1和b+1是关于x的方程x^2+qx+p=0的两根,求p,q的值. 已知关于x的方程2x^2+(log2m)x+log2根号m=0,有两个相等的实数根,求m的值 设关于方程4^x-2^x+1-b=0,若方程有实数解,求实数b的取值范围 以知关于X的方程 X的平方-pX+qX=0 的两个根是2和-3,求p,q的值. 若方程(1/2)^x=log2(X)的解为x1,方程(1/2)^x=log1/2X的解为x2,则x1x2的取值范围为若方程(1/2)的x次方=log以2为底x的对数的解为X1,若方程(1/2)的x次方=log以1/2为底X的对数的解为x2,则x1.x2的取值范围是什么 方程根号下(4-x^2)=lgx的根的个数是请把步骤写得清楚点,谢谢 如果x=3时,代数式px的3次方+qx+1的值为2008,则当x等于-3时px的3次方+qx+1 若x1,x2为方程【2的x次方=(1/2)的-1/x+1次方】的两个实数解,则x1+x2= 方程根号下4-x^2=lg x的根的个数4-x^2都在根号里 当x=-2时,代数式px的3次方+qx+1的值等于2012,那么当x=2时,代数式px+qx+1的值为多少?错了,当x=-2时,代数式px的3次方+qx+1的值等于2012,那么当x=2时,代数式px的3次方+qx+1的值为多少?四个选项A.2010 B.-201
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn