Android垃圾回收原理-流程篇

564 阅读4分钟
分配中gc

今天我们来接着之前内存相关的文章来看看Android ART虚拟机垃圾回收的流程。之前我分享过 Android 内存分配过程中,如果内存不足会发生gc:

这里会调用 heap.cc 里的 CollectGarbageInternal 方法。返回了本次gc的类型。

collector::GcType Heap::CollectGarbageInternal(
    collector::GcType gc_type,
    GcCause gc_cause,
    bool clear_soft_references,
    uint32_t requested_gc_num
)

GcType 枚举对应下面这几种:

enum GcType {
  // Placeholder for when no GC has been performed.
  kGcTypeNone,
  // Sticky mark bits GC that attempts to only free objects allocated since the last GC.
  kGcTypeSticky,
  // Partial GC that marks the application heap but not the Zygote.
  kGcTypePartial,
  // Full GC that marks and frees in both the application and Zygote heap.
  kGcTypeFull,
};

分别是不执行gc、回收本次分配的对象、回收ZtgoteSpace之外的对象、完整的回收。

这个方法还是比较长的,我按执行的步骤分部贴一下主要代码,主要步骤我这里就划分为

  • 判断是否执行gc
  • 确定收集器类型
  • 执行gc
  • 整理堆空间
  • 结束gc
判断是否执行
switch (gc_type) {
    case collector::kGcTypePartial: {
      if (!HasZygoteSpace()) {
        return collector::kGcTypeNone;
      }
      break;
    }
    default: {
    }
}
ScopedThreadStateChange tsc(self, ThreadState::kWaitingPerformingGc);
Locks::mutator_lock_->AssertNotHeld(self);
if (self->IsHandlingStackOverflow()) {
  gcs_completed_.fetch_add(1, std::memory_order_release);
  return collector::kGcTypeNone;
}

这里是完全没条件执行gc,所以返回 kGcTypeNone。

MutexLock mu(self, *gc_complete_lock_);
WaitForGcToCompleteLocked(gc_cause, self);
if (requested_gc_num != GC_NUM_ANY && !GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
  return collector::kGcTypeNone;
}

同一时间只能有一个gc在执行。

compacting_gc = IsMovingGc(collector_type_);
if (compacting_gc && disable_moving_gc_count_ != 0) {
  gcs_completed_.fetch_add(1, std::memory_order_release);
  return collector::kGcTypeNone;
}
if (gc_disabled_for_shutdown_) {
  cs_completed_.fetch_add(1, std::memory_order_release);
  return collector::kGcTypeNone;
}

gc被禁用。

compacting_gc

这里有一个 compacting_gc 的判断,我们看下他的具体实现:

当垃圾回收算法是 CC(并发复制)的时候,compactiing_gc 是true,从 compacting 这个词加上过去一些垃圾回收算法八股文的学习,我们可以推测,这个代表的应该是是否移动对象,压缩碎片空间的意思。

确定收集器类型

如果满足gc执行条件,那么需要确定一下当前的收集器类型。

如果是并发拷贝:

if (use_generational_cc_) {
  active_cc_collector = (gc_type == collector::kGcTypeSticky) ? young_concurrent_copying_collector_ : concurrent_copying_collector_;
  active_concurrent_copying_collector_.store(active_cc_collector,std::memory_order_relaxed);
  collector = active_cc_collector;
} else {
  collector = active_concurrent_copying_collector_.load(std::memory_order_relaxed);
}

这里根据分代回收的配置来确定cc的收集器:

这2个都是 ConcurrentCopying 这个收集器,但是创建的时候 yong_gen 参数不一样。

如果collector_type_是CMS(并发标记清理)的,那么会执行下面这个条件:

else if (current_allocator_ == kAllocatorTypeRosAlloc ||
  current_allocator_ == kAllocatorTypeDlMalloc) {
  collector = FindCollectorByGcType(gc_type);
}

这里的收集器类型一般为 RosAlloc 或者 DlMalloc。

f

会从 garbage_collectors 查找,garbage_collectors 里面一般是MarkSweep、PartialMarkSweep、StickyMarkSweep、SemiSpace 这几个收集器。他们定义的关系我列一下:

  • MarkSweep: kGcTypeFull + kCollectorTypeCMS
  • PartialMarkSweep: kGcTypePartial + kCollectorTypeCMS
  • StickyMarkSweep: kGcTypeSticky + kCollectorTypeCMS
  • SemiSpace: kGcTypePartial + kCollectorTypeSS

那么如果是 CMS 的,那么基本对应 MarkSweep 打头的这几个,而且这几个类的关系上也是继承关系:

(ps: 图方便随便画了一下继承关系,图里的箭头是父类指向了子类,不要理解错了)

执行垃圾回收

确定收集器之后执行gc,调用的都是GarbageCollector的 Run 方法:

collector->Run(gc_cause, clear_soft_references || runtime->IsZygote());

Run里面执行 RunPhases,这个方法就是每个收集器子类自己实现了。具体实现以后再分享。

整理堆空间

调用GrowForUtilization方法整理堆空间,这里面没什么重要的内容,最核心的地方就是根据gc类型来调整gc的触发阈值 concurrent_start_bytes:

结束gc

结束gc,返回本次gc类型。

FinishGC(self, gc_type);
clear->Run(self);
clear->Finalize();
Dbg::GcDidFinish();
return gc_type;
阈值触发gc

分析完分配中gc,我还注意到分配完成后也会再做一次内存大小检查,来决定是否触发gc,逻辑在 heap-inl.h 的 AllocObjectWithAllocator 函数:

这个地方就是和上文提到的阈值 concurrent_start_bytes__ _做对比。

如果需要gc,会调用 RequestConcurrentGCAndSaveObject 方法,进而调用 RequestConcurrentGC:

这里会给 task_processor_ 添加一个 ConcurrentGCTask, 他的Run方法会调 heap 的 ConcurrentGC:

最后仍然调用了CollectGarbageInternal 进行gc,所以CollectGarbageInternal 也是 ART虚拟机里垃圾回收的入口。

其他gc原因

CollectGarbageInternal 的参数里面有一个GcCause枚举,回溯前面2个case的代码,使用的case分别是 kGcCauseForAlloc 和 kGcCauseBackground。其他一些类型的cause我也随便列几个,但是就不一一分析了。实际上最常见的就是内存分配不足、内存到达gc阈值水平这两种。

  • kGcCauseExplicit: java层调用System.gc()
  • kGcCauseForNativeAlloc: NativeAllocationGcWatermark 超出的时候调用的,这个涉及到native分配的机制,感兴趣的也可以研究一下
  • kGcCauseCollectorTransition: 修改垃圾收集器类型
  • ..... 太多了,可以去 cs.android.com/android/pla… 自行查看