一、当前GC算法及存在的问题
当前Android(O-T)一直使用的是Concurrent Copy GC算法(CC),该算法通过FromSpace和ToSpace两个空间实现了非常巧妙的Copy机制,算是空间换时间(或者说复杂度),所以在GC的过程中,两个Space会同时被利用,极端情况会占用内存会double。
此外,为了保证GC过程中,访问对象的一致性,还引入的ReadBarrier,ReadBarrier会在访问对象的时候判断对象是否经过了拷贝,如果经过了拷贝需要返回拷贝后在ToSpace中的地址,反之,仍然返回拷贝前的地址。ReadBarrier会对每次对象访问,都插入一次是否访问的判断,即使当前没有在GC也会发生一次判断。这就导致了执行了多余的指令,且在编译结果二进制文件体积的增长。
总结一下,CC主要存在两个问题:
GC过程物理内存徒增又陡降
为保证一致性引入了ReadBarrier
二、新的Concurrent Mark Compact GC算法
Android马上要使能的新算法是Concurrent Mark Compact,简称CMC算法。CMC旨在解决上面介绍的CC算法存在的两个问题,其中最核心的是利用Linux的UFFD特性,并且更新了Allocator算法。
1、UFFD特性介绍
UFFD全称User Fault FD,其核心机制就是,先注册并监控虚拟内存范围的访问,当该范围的内存访问出现异常时,比如SIGBUS错误时,可以把对这块内存页的处理交接给用户空间。
此外,在没有发生SIGBUS错误时,也可以利用UFFD的ioctl特性进行目标内存页的处理。
2、BumpPointerSpace分配器
CMC对应的分配器是BumpPointerSpace,相对RegionSpace其结构更简单,也更利于全局的内存拷贝压缩。
3、GC拷贝压缩的准备工作
在CMC对象初始化的时候,也会初始化跟内存拷贝压缩的相关的数据结构。
在对Heap中对象进行全面遍历标记的时候,会根据对象是否存活,来记录和累加计算其压缩后的地址,还有被压缩后的各个内存页的首个对象,主要的数据结构有:
创建info_map,其中包含:
chunk_info_vec:记录存活对象的大小,所有该容器元素的累加和就是所有存活对象内存的大小。
first_objs_moving_space:会在PrepareForCompaction阶段,记录被压缩后某页的首个对象的源地址。
pre_compact_offset_moving_space:会记录页面被压缩后的地址偏移。
创建compaction_buffers_map:会被用于压缩过程中的页面级别的压缩,作为中转缓存,最后再通过UFFD特性被拷贝到目标地址页面。
① MarkingPhase阶段
该阶段会遍历所有的对象,并在处理存活对象的时候,调用UpdateLivenessInfo函数,该函数主要做了下面两个事情:
SetLiveWords记录存活地址和大小
chunk_info_vec批量记录活跃内存大小
② PrepareForCompaction阶段
该阶段比较重要的是InitMovingSpaceFirstObjects函数。该函数会找出每个需要移动的内存页的第一个对象,并找出这些对象应该被复制到的目标页偏移量
查找第一个live word,使用live_words_bitmap来找出第一个活动字的偏移量,然后计算出第一个活动对象的地址,并将第一个对象的地址和偏移量存储到first_objs_moving_space和pre_compact_offset_moving_space向量中。2.函数进入一个循环,找出每个需要移动的页的第一个对象和偏移量。并将它们存储到first_objs_moving_space和pre_compact_offset_moving_space向量中。
4、单页内存拷贝压缩
单页的拷贝主要是通过调用DoPageCompactionWithStateChange函数实现的,主要包含两个步骤:
CompactPage:拷贝到中转页
CopyIoctl:通过UFFD拷贝中转页到目标页地址
其中,CompactPage压缩的输入和处理过程如下:
输入
first_objs_moving_space[idx]压缩页的第一个对象pre_compact_offset_moving_space[idx]压缩页offset
处理过程
基于live_words_bitmap_来遍历内存页中的存活对象,逐步将数据复制到目标地址到中转页。
5、并行拷贝压缩
上一小节我们介绍了单页拷贝压缩的过程,全局的拷贝一般是从后往前的拷贝的,但是因为CMC也是并行的,所以GC过程中,非GC线程也就是Mutator线程还是会访问对象的。CC算法是通过ReadBarrier实现这个阶段数据一致性的,CMC是通过UFFD触发的SIGBUS特性实现的,也就是当访问一个对象时候,如果还没有被分配就会导致SIGBUS异常,这时候虚拟机会通过UFFD的SIGBUS异常获取缺页的地址,从而触发对应页的拷贝压缩。
总之,我们可以看到会有两种页拷贝和压缩:
GC线程串行压缩和拷贝
Mutator线程通过SIGBUS异常异步触发
而为保证页同时只会被一个线程处理,虚拟机通过ProcessState原子变量(每个页都有一个)保证异步并行,等所有页都被处理完后,GC过程就结束了。
6、其他内容
BlackAllocation(GC过程中新增的分配,被默认为黑色,不进行回收),通过SlidePage处理。
CompactPage和SlidePage中的跨页对象处理。
压缩后FromSpace页的清理等。
三、CMC算法的优缺点
1、优点
GC过程中RSS峰值降低
消除了ReadBarrier,BinarySize减小,Object访问性能提升
2、缺点
一次GC活跃的对象被拷贝两次
暂时没有实现YoungGC
缺页中断产生的时延可能会导致GC过程的性能退化
审核编辑:刘清