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

       通过前面一系列文章的学习,我们知道了ART运行时既支持Mark-Sweep GC,又支持Compacting GC。其中,Mark-Sweep GC执行效率更高,但是存在内存碎片问题;而Compacting GC执行效率较低,但是不存在内存碎片问题。ART运行时通过引入Foreground GC和Background GC的概念来对这两种GC进行扬长避短。本文就详细分析它们的执行过程以及切换过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       在前面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将参数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);
                    ......
                }
            }
        }

        ......
    }

    ......
}
       这个函数定义在文件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类的成员变量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);
}
       这个函数定义在文件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原先被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();
}
       这个函数定义在文件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简要介绍和学习计划一文,更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

<think>好的,我现在需要详细解释用户的问题:GC回收系统回收内存的区别。用户之前问过关于使用app_stop测试内存泄漏的问题,现在可能是在进一步理解内存管理机制。 首先,用户可能对这两个概念混淆,特别是Android环境下的内存管理。GC(垃圾回收)是Java虚拟机层面的,而系统回收可能指的是操作系统层面的内存管理。我需要明确两者的层次机制。 接下来,我需要对比两者的触发条件、作用范围、回收对象、速度、可控性等。例如,GC主要处理Java堆中的对象,而系统回收可能涉及进程级别的内存释放。还要注意GC是自动的,而系统回收可能在进程终止强制回收所有内存。 然后,用户可能想知道为什么在之前的回答中提到app_stop会掩盖内存泄漏,这里需要联系GC系统回收的不同作用。当使用app_stop,系统强制回收所有内存,包括泄漏的对象,而正常退出GC可能不会立即回收,从而暴露泄漏问题。 可能还需要举例说明,比如在Activity退出后,如果存在泄漏,GC后对象仍然存在,但系统回收会直接清除。这样用户能更直观地理解差异。 最后,要确保语言通俗易懂,避免过于技术化,同结构清晰,分点说明。可能需要使用表格对比,以及代码示例来辅助解释。</think>$\boxed{\text{GC回收 vs 系统级内存回收的本质区别}}$ --- ### **核心差异总结表** | 特征 | GC回收 (JVM/ART层面) | 系统级内存回收 (Linux内核层面) | |---------------------|-------------------------------|-------------------------------| | **作用层级** | 应用进程内部 | 整个操作系统 | | **触发条件** | 堆内存不足/主动调用 | 系统内存紧张/OOM Killer | | **回收对象** | Java/Kotlin对象引用 | 进程占用的物理内存页 | | **回收粒度** | 对象级 (精确到单个实例) | 进程级 (整块内存回收) | | **回收速度** | 毫秒级 (Stop-The-World暂停) | 微秒级 (直接操作物理内存) | | **可控性** | 可通过代码干预 (System.gc()) | 完全由内核调度控制 | | **泄漏检测有效性** | 可暴露未释放的引用链 | 强制回收所有内存(掩盖泄漏) | --- ### **GC回收机制详解** #### ▶ 工作流程(以Android ART为例) ```mermaid graph TD A[GC触发条件] --> B{堆使用量 > GC阈值?} B -->|是| C[暂停所有线程] C --> D[标记存活对象] D --> E[清除未标记对象] E --> F[内存压缩 (可选)] F --> G[恢复线程执行] ``` - **典型场景** ```java // 创建泄漏示例 class LeakActivity extends Activity { static List<Context> leaks = new ArrayList<>(); @Override void onCreate() { leaks.add(this); // 故意制造泄漏 } } // GC后验证 System.gc(); System.runFinalization(); assert LeakActivity.leaks.size() == 0; // 测试失败,说明存在泄漏 ``` #### ▶ 关键特点 1. **分代收集策略** - Young Generation (Minor GC): 回收短生命周期对象 - Old Generation (Major GC): 处理长期存活对象 - $\textcolor{blue}{\text{ART特有的CC(Concurrent Copying)收集器}}$ 2. **可达性分析算法** - 通过GC Roots(静态变量、活动线程等)构建对象引用链 - 公式表达: $Reachable = \{ o \mid \exists path\; from\; GC\; Roots\; to\; o \}$ 3. **Finalization机制** - 对象被回收前会调用finalize()方法 - $\textcolor{red}{\text{注意:finalize执行可能导致对象复活}}$ --- ### **系统级内存回收原理** #### ▶ Linux OOM Killer工作机制 ```python # 伪代码描述OOM Killer决策过程 def oom_killer(): processes = get_all_processes() badness_scores = [] for p in processes: score = p.memory_usage * p.oom_score_adj badness_scores.append((p, score)) target = max(badness_scores, key=lambda x: x[1]) send_signal(target.pid, SIGKILL) ``` #### ▶ Android特有优化 1. **Low Memory Killer (LMK)** - 按进程优先级预定义回收阈值: ```bash # /sys/module/lowmemorykiller/parameters/minfree 18432,23040,27648,32256,36864,46080 # 单位:page(通常4KB/page) ``` - 对应进程优先级:FOREGROUND > VISIBLE > SERVICE > BACKGROUND > EMPTY 2. **内存压缩(ZRAM)** - 使用LZ4算法压缩不常用内存页 - 公式估算节省空间: $Saving = \sum_{p\in P} (OriginalSize_p - CompressedSize_p)$ --- ### **实际场景对比验证** #### 实验代码(使用adb验证) ```bash # 场景1:触发GC回收 adb shell am dumpheap <PID> /data/local/tmp/gc.hprof hprof-conv gc.hprof gc_parsed.hprof # 用MAT分析对象残留 # 场景2:触发系统回收 adb shell am kill com.example.app # 模拟系统回收 adb shell procrank | grep com.example.app # 观察PSS归零 ``` #### 结果对比 | 指标 | GC回收后 | 系统回收后 | |--------------------|-------------------|------------------| | Java堆内存 | 可能残留泄漏对象 | 完全归零 | | Native内存 | 保持不变 | 完全释放 | | 进程状态 | 仍存在于进程列表 | 从进程表中移除 | | 重新启动速度 | 快速热启动 | 冷启动需要重新初始化 | --- $\boxed{\text{结论}}$ **GC回收**是应用层面的精细内存管理,适合检测代码层面的内存泄漏;**系统级回收**是粗暴的物理内存释放,会掩盖真实的内存问题。内存泄漏测试,必须依赖GC机制才能暴露真实问题。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值