关于GC碎碎念

目前大部分Android开发者能接触到的GC材料限制在八股文/关于Hotspot虚拟机的完结,大部分不适用ART虚拟机关于GC的完结,或许大部分材料只限制在GC的某个点,管中窥豹的办法很难有一个全貌。不了解ART虚拟机的进程也意味着当遇到GC相关的体系问题其实也很难解决。

即使ART虚拟机发展了这么多年,GC搜集这一块代码依旧在不断迭代当中,其间特殊情况下依旧有不少段错误的呈现,信任各位APP开发者也会遇到。

看完本文你将会学习到

  1. ART虚拟机中触及GC算法流程
  2. ART虚拟机是怎么依据不同的内存区域拟定不同的GC战略
  3. GC的全体大纲,以及后续怎么深化学习

学习完之后,这些常识能够被运用到:

  1. android功能优化战略
  2. 体系常识理解以及运用在自界说OS当中

阅览本文前,期望你已经掌握基本的GC算法的大致介绍,比方符号铲除,仿制等

ART中关于GC的完结

ART虚拟机的GC流程,从全体上看,其实首要围绕着以下几个点展开:

  1. GC战略:对接ART内存模型,GC战略怎么与ART内存模型对接
  2. GC规模:怎么确认GC的规模,全量扫描仍是部分扫描
  3. GC挑选:怎么依据场景挑选详细的废物收回器
  4. GC流程:符号流程与铲除流程,怎么采纳愈加速捷的办法让GC完结

GC战略

ART虚拟机是适用于Android这种手机的特殊定制化虚拟机,信任大家都知道这点,而ART的内存模型,其实决议着GC的战略。

这儿说的是一个战略的问题,比方某一块内存区域里边内容不简单/不发生废物目标,或许收回效益不大(GC的意图便是要开释更多的可用内存),其实咱们就能够不用参加GC的流程。假如某一块区域常常发生gc,那么天然,咱们就需求针对这一块区域进行GC,这样才能达到效益最大化。ART虚拟机其实便是依托这种思想,把进程堆的可用内存,分为了好几块。

这儿的内存块,由SpaceType表明

enum SpaceType {
  kSpaceTypeImageSpace,
  kSpaceTypeMallocSpace,
  kSpaceTypeZygoteSpace,
  kSpaceTypeBumpPointerSpace,
  kSpaceTypeLargeObjectSpace,
  kSpaceTypeRegionSpace,
};

它有以上几种,其间每种SpaceType的详细完结,也能够有多种,比方LargeObjectSpace的完结类依据32/64位架构不同,详细的完结分别为LargeObjectMapSpace/FreeListSpace等。无论这些完结类怎么,其实他们代表的space永远都是按照SpaceType表明。

ART虚拟机把内存依据不同的特征,抽象出一个个Space,它是一个抽象类,在创立每一个space的时分,会由详细的子类去依据自己的界说,经过GetType回来回来SpaceType,表明自己属于哪种内存类型

ART虚拟机GC 全貌

在这儿咱们先打住,咱们知道了ART虚拟机会把堆划分为不同的Space这一个现实,详细的Space承载着什么,能够阅览笔者之前关于ART虚拟机内存模型的解析。本章重点是GC模型,因而咱们只需求知道虚拟机有不同的内存块类型即可

在有了这些前置常识之后,咱们引出来第一个GC相关的概念,GC的战略

enum GcRetentionPolicy {
  // Objects are retained forever with this policy for a space.
  永远不GC
  kGcRetentionPolicyNeverCollect,
  // Every GC cycle will attempt to collect objects in this space.
  每次GC都会测验铲除该区域
  kGcRetentionPolicyAlwaysCollect,
  // Objects will be considered for collection only in "full" GC cycles, ie faster partial
  // collections won't scan these areas such as the Zygote.
  “full”gc条件下才会扫描该区域,跟GC的规模有关
  kGcRetentionPolicyFullCollect,
};

它为后续GC的规模奠定了扫描基础,举个例子,比方当时内存比较富余情况下,只需求对那些常常会发生废物目标的区域扫描即可,关于那些不怎么发生废物目标(有,可是相对少)的Space,就能够不同归入扫描规模,这样能让GC的整个扫描进程更快

GC的战略束缚的是Space,因而Space会在创立的时分,就会依据详细完结的差异,分别拟定不同的GcRetentionPolicy

下面我把Space与GcRetentionPolicy分别对应起来,就会得到下面表格。

ART虚拟机GC 全貌

ImageSpace kGcRetentionPolicyNeverCollect
MallocSpace kGcRetentionPolicyAlwaysCollect
ZygoteSpace kGcRetentionPolicyFullCollect
BumpPointerSpace kGcRetentionPolicyAlwaysCollect
LargeObjectSpace kGcRetentionPolicyAlwaysCollect
RegionSpace kGcRetentionPolicyAlwaysCollect

经过对Space设定不同的GC战略,能够十分高效确认GC的扫描规模,让每一次GC都依据自身的特性去扫描特定的Space,比方ZygoteSpace自身废物目标不多,因而只需求等到内存不太充足的FullGC条件下,才会被归入扫描规模。让GC自身的战略愈加高效

GC规模/类型

找到废物目标这个流程,其实算是一个较为耗时的进程,所以ART虚拟机的规划其实也跟咱们常见的思想相同。假如在内存较为富余的情况下,咱们其实能够只收回部分区域的废物目标即可,这样更快一起也能够较为有用的收回内存。在内存不足情况下,咱们收回战略便是尽或许的收回更多的内存,不然就会导致OOM对吧。这也是ART虚拟机的通用战略,只不过在一些情况下会有小部分的特殊处理算了

GcType目标就承担着GC的扫描规模规模这么一个进程

enum GcType {
  // Placeholder for when no GC has been performed.
  不进行GC
  kGcTypeNone,
  // Sticky mark bits GC that attempts to only free objects allocated since the last GC.
  针对前次存活的目标进行GC
  kGcTypeSticky,
  // Partial GC that marks the application heap but not the Zygote.
  针对heap一切进行GC不包括ZygoteSpace
  kGcTypePartial,
  // Full GC that marks and frees in both the application and Zygote heap.
  针对heap一切进行GC包括ZygoteSpace
  kGcTypeFull,
  // Number of different GC types.
  目前是充当鸿沟查看符号,即校验gctype是否在【kGcTypeNone,kGcTypeMax) 里边,由于gctype是外部传入
  kGcTypeMax,
  /*
  {
  比方heap 初始化时校验
    DCHECK_LT(gc_type, collector::kGcTypeMax);
    DCHECK_NE(gc_type, collector::kGcTypeNone);
  }
  */
};

各个GcType 对应的收回规模也不相同,而GcType由GarbageCollector这个废物收回器的基类决议。

GarbageCollector
virtual GcType GetGcType() const = 0;

这儿咱们其实就能够知道,GarbageCollector有着自己的GC规模,不同的GarbageCollector的完结,也决议了上述讲到的哪些Space内存会被参加扫描。

相同的,ART虚拟机也为GarbageCollector划分了不同的类型,即CollectorType,当然,这儿的type就描绘着GarbageCollector是基于哪种废物收回算法完结的,比方符号铲除,符号收拾,又或许是仿制算法等

enum CollectorType {
  // No collector selected.
  kCollectorTypeNone,
  // Non concurrent mark-sweep.
  kCollectorTypeMS,
  // Concurrent mark-sweep.
  kCollectorTypeCMS,
  // Concurrent mark-compact.
  kCollectorTypeCMC,
  // The background compaction of the Concurrent mark-compact GC.
  kCollectorTypeCMCBackground,
  // Semi-space / mark-sweep hybrid, enables compaction.
  kCollectorTypeSS,
  // Heap trimming collector, doesn't do any actual collecting.
  kCollectorTypeHeapTrim,
  // A (mostly) concurrent copying collector.
  kCollectorTypeCC,
  // The background compaction of the concurrent copying collector.
  kCollectorTypeCCBackground,
  // Instrumentation critical section fake collector.
  kCollectorTypeInstrumentation,
  // Fake collector for adding or removing application image spaces.
  kCollectorTypeAddRemoveAppImageSpace,
  // Fake collector used to implement exclusion between GC and debugger.
  kCollectorTypeDebugger,
  // A homogeneous space compaction collector used in background transition
  // when both foreground and background collector are CMS.
  kCollectorTypeHomogeneousSpaceCompact,
  // Class linker fake collector.
  kCollectorTypeClassLinker,
  // JIT Code cache fake collector.
  kCollectorTypeJitCodeCache,
  // Hprof fake collector.
  kCollectorTypeHprof,
  // Fake collector for installing/removing a system-weak holder.
  kCollectorTypeAddRemoveSystemWeakHolder,
  // Fake collector type for GetObjectsAllocated
  kCollectorTypeGetObjectsAllocated,
  // Fake collector type for ScopedGCCriticalSection
  kCollectorTypeCriticalSection,
};

详细的废物收回器都是GarbageCollector的子类,假如咱们想要研究某一类废物收回算法在ART的完结,能够经过阅览对应子类的完结进行了解

ART虚拟机GC 全貌

废物收回器中会依据详细的完结,界说好详细的GC规模,一起GC规模也间接影响了哪些Space会被参加废物收回。值得一提,废物收回器也决议了详细分配器的完结,不同的废物收回也对应着不同的内存分配器,这儿简单提一下,Heap::ChangeCollector

GC挑选

咱们知道到了废物收回器的相关特征,那么ART虚拟机是怎么挑选哪种废物收回器的呢?总不或许都挑选一切废物收回器吧。

影响废物收回器的挑选能够首要有三个大的要素

  1. 废物收回器的默许挑选:功能突破,比方在早期Android版别挑选CMS进行废物收回,而后续高版别中默许采纳CC分配办法,由于它功能更好,暂停时间更短等。
  2. 厂商自界说:比方OS厂商,会有部分厂商测验自研废物收回完结,贴合自研的体系,因而能够忽略默许选项,经过自界说“-Xgc”类型选中特定的废物收回器,比方在一些车企OS中会指定即使android高版别的体系通用采纳CMS(毕竟CMS经过内部迭代多版)
  3. 前后台:应用前后台不同(这儿指ProcessState)区分,比方前台由于直接与用户进行交互,就会挑选废物收回更快的办法,而后台由于用户看不到,就能够挑选把内存收拾一下,腾出更多接连内存(避免内存颤动)

废物收回器的决议会在Heap初始化的时分,挑选废物收回器,需求指定前台废物收回器与后台废物收回器

ART虚拟机GC 全貌

其间会依据是否使用读屏障选中默许的CC仍是Xgc参数拟定的废物收回器类型(目前手机AndroidOS大部分都是使用读屏障(其实它的是一个mutex,会在编译被嵌入相关的指令))

ART虚拟机GC 全貌

ART虚拟机GC 全貌
自界说gctype会更具-Xgc参数赋值

ART虚拟机GC 全貌

当然,当发现前后台切换时,会经过UpdateProcessState函数,改动前后台Collecter的切换

void Heap::UpdateProcessState(ProcessState old_process_state, ProcessState new_process_state) {
  if (old_process_state != new_process_state) {
    前台jank_perceptible true  
    const bool jank_perceptible = new_process_state == kProcessStateJankPerceptible;
    if (jank_perceptible) {
      // Transition back to foreground right away to prevent jank.
      RequestCollectorTransition(foreground_collector_type_, 0);
      GrowHeapOnJankPerceptibleSwitch();
    } else {
      // 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. Similarly, we invoke a full compaction for kCollectorTypeCC but don't
      // transition the collector.
      RequestCollectorTransition(background_collector_type_, 0);
    }
  }
}

废物收回器需求指定前后台废物收回器,总的大纲便是,在前台环境下,用户关于卡顿会愈加灵敏,因而需求挑选更快的废物收回,而后台环境下,卡顿不灵敏,因而需求进行内存的收拾,便于内存块的整合

GC流程

抛开System.gc引起的主动gc,大部分GC由ConcurrentGCTask与分配时AllocInternalWithGc触发,咱们简单看一下ConcurrentGCTask,分配GC咱们在内存海绵计划中介绍过了(关于GC流程的追踪,咱们就能够经过这个Task触发进行调用链分析)

class Heap::ConcurrentGCTask : public HeapTask {
 public:
  ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full, uint32_t gc_num)
      : HeapTask(target_time), cause_(cause), force_full_(force_full), my_gc_num_(gc_num) {}
  void Run(Thread* self) override {
    Runtime* runtime = Runtime::Current();
    gc::Heap* heap = runtime->GetHeap();
    DCHECK(GCNumberLt(my_gc_num_, heap->GetCurrentGcNum() + 2));  // <= current_gc_num + 1
    heap->ConcurrentGC(self, cause_, force_full_, my_gc_num_);
    CHECK_IMPLIES(GCNumberLt(heap->GetCurrentGcNum(), my_gc_num_), runtime->IsShuttingDown(self));
  }
 private:
  const GcCause cause_;
  const bool force_full_;  // If true, force full (or partial) collection.
  const uint32_t my_gc_num_;  // Sequence number of requested GC.
};

进行一系列的纠正

void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num) {
  if (!Runtime::Current()->IsShuttingDown(self)) {
    // Wait for any GCs currently running to finish. If this incremented GC number, we're done.
    WaitForGcToComplete(cause, self);
    if (GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
      collector::GcType next_gc_type = next_gc_type_;
      // If forcing full and next gc type is sticky, override with a non-sticky type.
      if (force_full && next_gc_type == collector::kGcTypeSticky) {
        next_gc_type = NonStickyGcType();
      }
      // If we can't run the GC type we wanted to run, find the next appropriate one and try
      // that instead. E.g. can't do partial, so do full instead.
      // We must ensure that we run something that ends up incrementing gcs_completed_.
      // In the kGcTypePartial case, the initial CollectGarbageInternal call may not have that
      // effect, but the subsequent KGcTypeFull call will.
      if (CollectGarbageInternal(next_gc_type, cause, false, requested_gc_num)
          == collector::kGcTypeNone) {
        for (collector::GcType gc_type : gc_plan_) {
          if (!GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
            // Somebody did it for us.
            break;
          }
          // Attempt to run the collector, if we succeed, we are done.
          if (gc_type > next_gc_type &&
              CollectGarbageInternal(gc_type, cause, false, requested_gc_num)
              != collector::kGcTypeNone) {
            break;
          }
        }
      }
    }
  }
}

堆建议,并进行校验,判断是否真正建议一次GC,并更新下一次gc的类型(比方当时是全量gc且下次gc为sticky情况下,下次gc旧设置为null,节约功率)

force_full 对应的是fullgc 或许 partial gc 取决于废物收回器

这儿面便是一个功率问题,尽或许触发短快的stick或许一定条件答应下不触发,建议后由对应collect决议。

终究调用CollectGarbageInternal办法,里边会由GarbageCollector的详细子类进行废物收回,调用其run办法

ART虚拟机GC 全貌

废物收回器首要做的其实便是两件事:

  1. 符号可达的内存
  2. 删除不可达的内存

由于符号可达内存这一步,能够采纳多线程的办法进行符号,所以一些Concurrent前缀的战略,其实便是采纳多线程的办法加速符号。

符号这儿会触及两个处理:

  1. 去增:不要让新的内存进行分配,由于新内存分配简单改动引证链,让其愈加复杂,因而大多数采纳读锁获取的办法,能让分配器能够读取已有的内存,不至于让gc卡顿,注意,写锁获取仍然被堵塞,比方分配内存。
  2. 减存:经过获取写锁,把堆中的废物目标铲除,获取写锁便是避免让无效的被开释的内存还能被读取或许使用。这一步往往是ART GC的最难的一步,假如发生异常,那么很简单发生段错误SIGSEGV (signalsegmentation violation)比方jni操作数组越界后,会破坏jvm的引证链,形成gc时SIGSEGV。

所以废物收回器的本质都是在这两点基础上做出差异优化,咱们拿手机os中默许也是干流的ConcurrentCopying废物收回器进行解析

void ConcurrentCopying::RunPhases() {
  CHECK(kUseBakerReadBarrier || kUseTableLookupReadBarrier);
  CHECK(!is_active_);
  is_active_ = true;
  Thread* self = Thread::Current();
  thread_running_gc_ = self;
  Locks::mutator_lock_->AssertNotHeld(self);
  {
    这儿获取读锁,也便是说,假如有分配的话才会堵塞,没有就不会堵塞正常流程
    ReaderMutexLock mu(self, *Locks::mutator_lock_);
    初始化符号,这儿会把目标Space与非目标Space进行符号与分离,这儿符号的是上一次存活的目标
    InitializePhase();
    // In case of forced evacuation, all regions are evacuated and hence no
    // need to compute live_bytes.
    if (use_generational_cc_ && !young_gen_ && !force_evacuate_all_) {
      假如启用了分代并且不是年青代且force_evacuate_all_符号位为false,再次发动符号
      MarkingPhase();
    }
  }
  if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
    // Switch to read barrier mark entrypoints before we gray the objects. This is required in case
    // a mutator sees a gray bit and dispatches on the entrypoint. (b/37876887).
    ActivateReadBarrierEntrypoints();
    // Gray dirty immune objects concurrently to reduce GC pause times. We re-process gray cards in
    // the pause.
    ReaderMutexLock mu(self, *Locks::mutator_lock_);
    GrayAllDirtyImmuneObjects();
  }
  FlipThreadRoots();
  {
    ReaderMutexLock mu(self, *Locks::mutator_lock_);
    CopyingPhase();
  }
  // Verify no from space refs. This causes a pause.
  if (kEnableNoFromSpaceRefsVerification) {
    TimingLogger::ScopedTiming split("(Paused)VerifyNoFromSpaceReferences", GetTimings());
    ScopedPause pause(this, false);
    CheckEmptyMarkStack();
    if (kVerboseMode) {
      LOG(INFO) << "Verifying no from-space refs";
    }
    VerifyNoFromSpaceReferences();
    if (kVerboseMode) {
      LOG(INFO) << "Done verifying no from-space refs";
    }
    CheckEmptyMarkStack();
  }
  {
    ReaderMutexLock mu(self, *Locks::mutator_lock_);
    ReclaimPhase();
  }
  FinishPhase();
  CHECK(is_active_);
  is_active_ = false;
  thread_running_gc_ = nullptr;
}

经过**CheckEmptyMarkStack**();

循环符号,尽或许减少分配线程中存留目标或许引证到了其他目标

最后仍是要上写锁,把内存搬迁

ART虚拟机GC 全貌

当然,这儿触及的gc算法,比方染色、读屏障就不详细分析了,这块材料仍是有很多的。

GC流程本质便是依据内存情况进行内存删减的流程,这儿面便是调用详细的废物收回器进行搜集,整个废物收回器的规划中心都是两部,怎么更快且更准确做出去增减存

总结

ART中GC还有不少的内容,不过信任读完这篇文章,咱们对GC有了一个愈加宏观的全貌,接下来只需求依据需求进行细节的阅览即可。当然,了解后,咱们能够在这基础上,开宣布各种各样的gc黑科技,从而让APP功能有多的或许。

我是Pika,假如你喜欢我的文章,请不要忘掉点赞重视!后面还有一系列鸿蒙/Android文章等你!