Android ART 并行拷贝垃圾回收

ART从Android O 开始提供了新的并行拷贝垃圾回收器(CC).本文基于最新的Android Q的代码对垃圾回收过程进行分析.

CC垃圾回收过程可分为6个阶段,分别为InitializePhase,MarkingPhase,FlipThreadRoots,CopyingPhase,ReclaimPhase和FinishPhase.(见art/runtime/gc/collector/concurrent_copying.cc : ConcurrentCopying::RunPhases)

0.背景知识

ART将heap划分为spaces进行管理(Fig.1),对Space中的对象进行划区(Card,一个Card包含多个对象),Card由CardTable来维护,对堆地址的写操作会经由CardTable反映到Card的状态上。比如,修改了一个对象的成员的指向,则该对象所在的Card会被标记为Dirty。live_bitmap_用来标记上次GC以来存活的对象,mark_bitmap_用来标记本次GC后存活的对象,allocation_stack_用来记录上次GC后分配的对象,live_stack_在GC过程中作为allocation_stack_的替身,使得分配过程可以无锁处理allocation_stack_。Mod Union Table记录一个Space中的对象对另一个Space中对象的引用。

Fig.1 Space

1. InitializePhase

196    ReaderMutexLock mu(self, *Locks::mutator_lock_);
197    InitializePhase();

初始化阶段主要是初始化统计量,设置标记等,这里主要介绍:

a) BindBitmaps: 将Image Space和Zygote Space加入到immune_spaces中表示不会回收;对于分代垃圾回收(Generational CC collection),如果是sticky GC(young_gen_),将Non-Moving space和Large Object Space的live_bitmap_设为mark_bitmap_,因为sticky GC只会对上次回收以来分配的对象进行回收;将Region Space和Non-moving Space的Card进行Age操作(kCardDirty-1),后面CopyingPhase会扫描Aged Card。如果是Full GC,清空Region Space和Non-Moving Space的Card Table。如果不是分代垃圾回收,清空Region Space的mark_bitmap_。

b) MarkZygoteLargeObjects: 遍历Large Object Space中自上次GC以来存活的对象,如果是zygote中的对象,则标记到其mark_bitmap中,防止回收。

2. MarkingPhase

200    if (use_generational_cc_ && !young_gen_ && !force_evacuate_all_) {
201      MarkingPhase();
202    }

该阶段用来标记对象,此阶段不是必须的。对于sticky GC,初始化阶段已经设置了mark_bitmap_所以不需要继续mark;Region Space分为from space和to space,from space又分为kRegionTypeUnevacFromSpace和kRegionTypeFromSpace,GC Copy阶段会把kRegionTypeFromSpace中的对象拷贝到to space中,释放from space用于后续分配,但是kRegionTypeUnevacFromSpace中的对象不进行拷贝,这样做是为了避免不必要的拷贝工作。一个Space被视为kRegionTypeUnevacFromSpace是通过其中live object bytes占该Space总容量的比例等因素决定的,而这依赖于MarkingPhase的工作。force_evacuate_all_会强制转移所有from space中的对象,所以也不需要此阶段的工作。

被标记的对象有以下几个来源:

a) immune spaces

Immune spaces中的对象如果引用了其他space中的对象,相应的Card会被标记为kCardDirty(这是通过write barrier实现的)。这里会遍历immune space中所有的dirty cards中的对象,将其引用的对象标记到相应space的mark_bitmap_中,并且push到回收器类ConcurrentCopying的gc_mark_stack_成员中,用于后续递归处理其引用的其他对象。

b) runtime roots

  1. InternTable中的String
  2. ImageHeader::kClassRoots以及boot class table中的对象。

c) Non-thread root

  1. VM中的全局对象,比如WellKnownClassLoader,用于保存被回收引用的cleared_references_,即将进行Jit编译的方法所属的类,进行Jit Profile的Dex File对应的class loader等。
  2. sentinel_,pre_allocated_OutOfMemoryError_when_throwing_exception_,pre_allocated_OutOfMemoryError_when_throwing_oome_,pre_allocated_OutOfMemoryError_when_handling_stack_overflow_,pre_allocated_NoClassDefFoundError_
  3. Image Roots,比如kDexCaches(java.lang.DexCache(s)),kClassRoots(java.lang.Class,java.lang.Object,java.lang.String,java.lang.String等等)
  4. 基础类,比如UndefinedType,ConflictType,NullType,BooleanType,ByteType,ShortType,CharType等等
  5. 用于在编译时提前初始化class的Transactions对象

d) thread root

包括thread的成员属性,比如表示thread local storage的tlsPtr_的成员;线程函数调用栈中的对象等。

e) ProcessMarkStack

对上面push到gc_mark_stack_中的对象递归处理。

3. GrayAllDirtyImmuneObjects

204  if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
210    ReaderMutexLock mu(self, *Locks::mutator_lock_);
211    GrayAllDirtyImmuneObjects();
212  }

遍历Immune space的dirty card,将其中的对象标记为gray状态。这个过程是在 ReaderMutexLock mu(self, *Locks::mutator_lock_)的保护下进行的。

Space中对象的状态分为kGrayState和kNonGrayState(相应的设置操作我们称为gray和unGray)。对于kGrayState的对象,如果访问时指定了kWithReadBarrier,并且被访问的引用处于from space中,那么访问结束后,该引用对象会被Copy到to space中(Fig.2),不在from space中的对象会进行相应的gray以及标记操作,并push到gc_mark_stack_中,后面CopyingPhase会继续递归处理。Read barrier的目的是实现GC过程中mutator thread与GC thread的并行,以减少停顿。

Fig.2 Read Barrier

4. FlipThreadRoots

213  FlipThreadRoots();

标记线程中的根集对象,同时会将其中from space中的对象拷贝到to space中,并完成引用的更新(Fig.3)。图中gc_mark_stack_中的引用会在CopyingPhase中还会用到,以递归处理根集对象引用的其他对象;immune_gray_stack_中的引用会在CopyingPhase中进行unGray的操作,因为这里进行了gray操作。

Fig.3 FlipThreadRoots

5. CopyingPhase

214  {
215    ReaderMutexLock mu(self, *Locks::mutator_lock_);
216    CopyingPhase();
217  }

该阶段核心任务有两个,任务一是完成Region From Space到To Space的拷贝,同时对被引用的对象进行标记(设置到mark_bitmap_,ReclaimPhase需要使用此信息);任务二是防止拷贝过程中from space中的对象其他space被引用,因为GC结束后from space会被释放,因此该阶段是GC中比较耗时的一个阶段。由于Space之间和Space内部的对象可以相互引用,因此处理过程也比较复杂。

对不同Space中的对象处理方式如下:

5.1 Region Space

a) kRegionTypeToSpace

已经在to space中,直接返回该引用。

b) kRegionTypeFromSpace

在from space中,将其拷贝到to space中。Object对象有一个 uint32_t 类型的monitor_成员,该值被封装成一个LockWord用于表示对象的状态,当from_ref(待拷贝的对象)被Copy为to_ref后,to_ref的地址会被填充到from_ref的monitor成员中,并更改其状态为LockWord::kForwardingAddress。填充过程采用CAS方式,实现原子操作。

3448    // Try to atomically write the fwd ptr.
3449    bool success = from_ref -> CasLockWord(old_lock_word,
3450                                         new_lock_word,
3451                                         CASMode::kWeak,
3452                                         std::memory_order_relaxed);

在实际填充之前,需要首先判断是否已经填充过了,以防止重复Copy。

3397    if (old_lock_word.GetState() == LockWord::kForwardingAddress) {
3424      // Get the winner's forward ptr.
3425      mirror::Object* lost_fwd_ptr = to_ref;
3426      to_ref = reinterpret_cast<mirror::Object*>(old_lock_word.ForwardingAddress());
3432      return to_ref;
3433    }

c) kRegionTypeUnevacFromSpace

对于分代垃圾回收,如果当前没有扫描完成,直接设置该对象为Gray State,并push到gc_mark_stack_中;其他情况下需要判断是否已经在mark_bitmap_中标记,如果没有标记则加以标记,并push到gc_mark_stack_中。对于kUseBakerReadBarrier的情况还需要设置对象状态为Gray State。gc_mark_stack_中的对象后面会被递归处理。

5.2 Non-Moving Space

如果当前扫描过程还没有结束(!done_scanning_),则设置对象状态为Gray State,并加入到gc_mark_stack_中,返回。

如果已经在mark_bitmap_中标记,直接返回。否则进行标记(对于kUseBakerReadBarrier,就是设置Gray State,否则在mark_bitmap_中标记),标记完成加入到gc_mark_stack_中等待处理。此外一个对象在Allocation Stack中也会被视为已经标记。

5.3 Large Object Space

同Non-Moving Space

5.4 immune space

设置对象为Gray State,并加入到immune_gray_stack_中

对于任务一,接下来只需要处理 runtime roots和Non-thread root,并且递归处理mark stack中的引用,进行拷贝操作即可。

对于任务二,需要遍历其他Space中可能引用到Region Space中kRegionTypeFromSpace的引用,进行Mark处理(拷贝或gray),需要注意的是Region Space中kRegionTypeUnevacFromSpace也需要处理。

最后ProcessReferences,具体逻辑如下:如果指定不清理soft reference,则对soft reference queue中的对象进行Mark操作,这个过程可能会将新的对象加入到gc_mark_stack_中,所以随后需要调用ProcessMarkStack进行递归处理。将soft reference queue,weak reference queue,phantom reference queue,finalizer reference queue中的white object(只被reference对象引用)进行ClearReferent操作(即置成员referent_为null)并添加到cleared_references_中,由于finalizer reference queue中的对象还会调用其finalize方法,因此需要进行Mark操作。cleared_references_中的对象会被加入ReferenceQueue中,并唤醒ReferenceQueueDaemon线程,将被回收的对象加入到与其关联的ReferenceQueue中。这进一步会唤醒FinalizerDaemon线程,回调对象的finalize方法。

Fig.4 CopyingPhase

6. ReclaimPhase

232  {
233    ReaderMutexLock mu(self, *Locks::mutator_lock_);
234    ReclaimPhase();
235  }

a) ClearFromSpace

调用syscalls释放from space以及对应live bitmap的物理内存,对于UnevacFromSpace,如果其中的对象全部dead,也予以释放。

遍历每个region,重置其状态(RegionState::kRegionStateFree),使其可用于后续分配。同时把Unevac From Space设置为To Space。

如果一个Region From Space的空间内都是live object,对于非分代垃圾回收,则清空其live bitmap,后面可以通过对象及其大小来遍历。对于分代垃圾回收,因为minor垃圾回收 (young-generation) 需要知道哪些objects是在上次GC cycle中mark的,所以不能清空live bitmap。

对于非from space,将dead object内存填充为kPoisonDeadObject(0xBADDB01D).

b) Sweep

遍历zygote space,non-moving space和Large Object Space。

对于sticky回收,对在live_stack_中,但不在mark bitmap中的对象进行回收。为了提高回收效率,每收集满kSweepArrayChunkFreeSize个对象在进行批量回收。

对于非sticky回收, 将live_stack_中的对象(上次GC至今分配的对象)标记到mark_bitmap中,对在live_bitmap_但不在mark_bitmap_中的对象调用mspace_bulk_free进行批量释放。

7. FinishPhase

236  FinishPhase();

对于分代垃圾回收,如果不是minor GC,清空region_space_inter_region_bitmap_和non_moving_space_inter_region_bitmap_,这两个分别用来记录region space到region space以及Non-moving space到region space的引用,在CopyingPhase阶段对其进行处理过,以防止引用到from space中的对象,现在可以清空了。

清空mark_bitmap_归还内存。

将rb_mark_bit_stack_ 中的对象mark state清除,并释放rb_mark_bit_stack_。rb_mark_bit_stack_主要是用来快速判断一个对象是否被marked,通过read barrier被mark的对象会加入到其中,容器大小限制在kReadBarrierMarkStackSize = 512 * KB。

8. Debug

systrace

如图Fig.5,显示了触发垃圾回收的原因(这里是Background)和垃圾回收类型,以及各个阶段的信息。

Fig.5 GC systrace

log

adb shell kill -3 PID
生成/data/anr​/trace_xx文件,内容如下:

Dumping cumulative Gc timings
Start Dumping histograms for 7 iterations for concurrent copying
ProcessMarkStack: Sum: 677.865ms 99% C.I. 17.547ms-289.696ms Avg: 96.837ms Max: 293.222ms
MarkingPhase: Sum: 390.390ms 99% C.I. 40.572ms-106.288ms Avg: 65.065ms Max: 106.653ms
.............
(Paused)FlipCallback: Sum: 34us 99% C.I. 2us-13us Avg: 4.857us Max: 13us
FlipThreadRoots: Sum: 30us 99% C.I. 2us-10us Avg: 4.285us Max: 10us
UnBindBitmaps: Sum: 7us 99% C.I. 0.250us-3us Avg: 1us Max: 3us
Done Dumping histograms
concurrent copying paused: Sum: 2.654ms 99% C.I. 92us-1148us Avg: 379.142us Max: 1148us
concurrent copying freed-bytes: Avg: 22MB Max: 31MB Min: 9075KB
Freed-bytes histogram: 7680:1,20480:3,23040:1,25600:1,30720:1
concurrent copying total time: 1.721s mean time: 245.929ms
concurrent copying freed: 2304727 objects with total size 154MB
concurrent copying throughput: 1.33918e+06/s / 89MB/s  per cpu-time: 110655645/s / 105MB/s
Average major GC reclaim bytes ratio 0.580458 over 7 GC cycles
Average major GC copied live bytes ratio 0.441322 over 12 major GCs
Cumulative bytes moved 54325344
Cumulative objects moved 1114709
Peak regions allocated 673 (168MB) / 2048 (512MB)

编辑于 02-20