首页  

java GC 进化     所属分类 ZGC 浏览量 1294
Serial           标记-复制
SerialOld        标记-压缩
ParNew           标记-复制
ParallelScavenge 标记-复制
ParallelOld          标记-压缩
CMS(Concurrent-Mark-Sweep)(并发)标记-清除
G1(Garbage-First) 并发标记 + 并行复制    JDK9开始作为默认的垃圾回收器
ZGC/C4             并发标记 + 并发复制
ShenandoahGC       并发标记 + 并发复制



G1 分代 大小相等的分区 Region 新生代 全部回收 Young GC 老年代 部分回收 选择一部分收益最高的 Region 待回收的老年代 Region 和所有的新生代 Region 放在一起进行回收 Mixed GC 初始标记阶段(STW) 并发标记阶段 再标记阶段(STW) 清理阶段(STW) 为降低标记 STW 时间,使用记录集(Remembered Set, RSet),记录不同代际之间的引用关系 ,减小 GC 堆扫描范围 使用 SATB(Snapshot-At-The-Beginning) 记录并发标记时引用关系的改变,保证并发结束后引用关系的正确性。 实现 RSet 和 SATB 的关键 写屏障 写屏障分为 pre_write_barrier 和 post_write_barrier 字段更新 赋值之前会触发 pre_write_barrier,更新 SATB 日志记录,记录下引用关系变化时旧的引用值 赋值之后,会执行 post_write_barrier,更新新引用对象所在的 RSet // 赋值操作 void assign_new_value(oop* field, oop value) { pre_write_barrier(field); *field = value; post_write_barrier(field, value); } 写屏障实现 线程队列+全局队列的两级结构 RSet 谁引用了我 写屏障 -> 线程队列 -> 全局队列 -> 并发 RSet 更新 Region 内部的引用:无需记录,因为垃圾回收时 Region 内对象肯定要扫描的; 新生代 Region 间的引用:无需记录,因为新生代在 Young GC 和 Mixed GC 中都会被整体回收: 老年代 Region 间的引用:需要记录,因为老年代回收时是按 Region 进行回收的,因此需要记录; 新生代 Region 到老年代 Region 的引用:无需记录,Mixed GC 中会把整个新生代作为 GC Roots; 老年代 Region 到新生代 Region 的引用:需要记录,Young GC 时直接将这种引用加入 GC Roots。 SATB 对象引用关系快照 SATB 记录更新由 pre_write_barrier 写屏障触发 两级的队列结构缓存,再由并发标记线程批量处理进入标记队列 satb_mark_queue void pre_write_barrier(oop* field) { oop old_value = *field; if (old_value != null) { if ($gc_phase == GC_CONCURRENT_MARK) { $current_thread->satb_mark_queue->enqueue(old_value); } } } 并发标记后还有一个需要 STW 的再标记(remark) 再标记阶段,只需要扫描 satb_mark_queue 队列里的引用变更记录就可以对此次 GC 活动形成完整标记 G1的缺点 堆利用率不高 引入的 RSet 占用内存空间较大,一般 1%~20% 暂停时间较长 STW 时间 几十到几百毫秒 暂停时间主要来自 标记阶段结束后的 Region 复制(一般占用整个 GC STW 的 80%) 这个阶段使用的复制算法 把一部分 Region 里的活的对象复制到空 Region 里去,然后回收原来的 Region的空间 该过程无法并发(并发复制一般需要通过 读屏障 来实现,G1 并未使用) 因为需要一边移动对象,同时一边修正指向这些对象的引用(并发期间应用线程可能会访问到这些对象) G1 虽然在复制对象时也做到了并行化,但大量对象的复制会涉及到很多内存分配、变量复制的操作,非常耗时。
ZGC 2017 年 Oracle 将 ZGC 贡献给 OpenJDK 社区,2018年 JEP-333 正式引入 ZGC: A Scalable Low-Latency Garbage Collector (Experimental) ZGC 的设计思路借鉴了一款商业垃圾回收器 Azul Systems公司的 C4(Continuously Concurrent Compacting Collector) C4 分代 并发 协作式 ZGC 和 C4 背后的算法是 Azul Systems 很多年前提出的 Pauseless GC 区别在于 C4 是一种分代的实现,而 ZGC 现在还是不分代的 ZGC 标记-复制算法的并发实现 复制阶段又分为转移(Relocate)和重定位(Remap) 全程并发,暂停时间保持在10ms以内 标记和复制看上去是两个串行的阶段,其实也是有重叠的 mark - copy (Relocate Remap) 并发过程 引用关系改变 引入 SATB , 通过 读屏障 + 多视图映射 来实现 SATB (G1 通过写屏障 实现 SATB) 引入读屏障后,GC 线程可以并发执行,读取的引用如果发生了转移或者修改,可以在读屏障内完成内存的转移或者重定位 load_barrier_on_oop_field_preloaded inline oop ZBarrierSet::AccessBarrier<decorators, BarrierSetT>::oop_load_in_heap(T* addr) { verify_decorators_absent<ON_UNKNOWN_OOP_REF>(); const oop o = Raw::oop_load_in_heap(addr); return load_barrier_on_oop_field_preloaded(addr, o); } 多视图映射 ZGC 将内存划分成小的分区,在ZGC中称为页面(page), 但是 ZGC 中的页面大小并不是固定的,分为小页面、中页面和大页面, 其中小页面大小为 2MB,中页面大小为 32MB,而大页面则和操作系统中的大页面的大小一致 同一物理地址的对象可以映射到多个虚拟地址上,虚拟地址有 Marked0、Marked1 和 Remapped 三种 三个虚拟空间在同一时间点有且仅有一个空间有效 对象引用(指针) 增加元数据信息 着色指针(Color Pointers) 前42位保留为对象的实际地址 42位地址理论上提供了4TB的堆限制 其余的位用于标记 Finalizable、Remapped、Marked1 和 Marked0 (保留一位以备将来使用) 41-0 Object Offset (42-bits, 4TB address space) 45-42 Metadata Bits (4-bits) 0001 = Marked0 0010 = Marked1 0100 = Remapped 1000 = Finalizable 46-46 Unused (1-bit, always zero) 63-47 Fixed (17-bits, always zero) 多视图映射 加快标记和转移的速度 标记阶段,标记某个对象时只需要转换地址视图即可, 地址视图的转化非常简单,只需要设置地址中第42~45位中相应的标记位即可 以前的垃圾回收器实现中,需要修改相应对象头的标记位,而这会有内存存取访问的开销 读屏障 SATB 多视图映射 ZGC并发算法核心点 SATB 保证在并发标记和并发复制阶段引用变更的正确性 并发标记阶段,通过标记引用(指针)实现对对象的遍历 并发转移阶段,读屏障保证并发转移时应用线程读出的指针为对象的新地址 并发重定位阶段,读屏障保证应用线程可以获取到转移后的对象的新地址 ZGC 在指针上做标记,访问指针时加入 Load Barrier(读屏障), 当对象正被 GC 移动,指针上的颜色就会不一样,这个屏障就会先把指针更新为有效地址再返回 ZGC 虽然是全程并发设计的,但还是有若干个 STW 阶段, 包括并发标记中的初始化标记和结束标记阶段,并发转移中的初始转移阶段等。 完全没有 STW 的垃圾回收器是不存在的, 即便是 Azul 的 PGC(原汁原味基于 Pauseless GC 算法实现), 也有非常短暂的 STW 阶段,譬如 GC Roots 的扫描
Shenandoah ShenandoahGC 最早由 Red Hat 公司发起, 后来贡献给 OpenJDK,2014 年通过 JEP-189:A Low-Pause-Time Garbage Collector (Experimental)正式成为 OpenJDK 的开源项目, ShenandoahGC 出现的时间比 ZGC 要早很多,因此发展的成熟度和稳定性相较于 ZGC 来说更好一些, 实现了包括括C1屏障、C2屏障、解释器、对 JNI 临界区域的支持等特性 ZGC实现并发复制的关键 读屏障 + 基于着色指针(Color Pointers)的多视图映射 ShenandoahGC 实现并发复制的关键 读写屏障 + 转发指针(Brook Pointers) Shenandoah GC 的 回收周期和 ZGC 非常类似,大致也可以分为并发标记和并发复制两个阶段, 在并发标记阶段,也是通过 读屏障+ SATB 来实现的,并发复制阶段也分为并发转移和并发重定位两个子阶段。 使用读屏障+转发指针保证转移过程中或转移结束后,应用线程可以读取到真实的引用地址,保证了数据的一致性 ShenandoahGC 并发复制基于读屏障+写屏障共同实现( ZGC 只使用了读屏障)
GC 技术趋势 支持大内存 更低的STW 时间 ,通过并发 降低 GC算法在标记和转移对象时对应用程序的影响 CMS 并发标记 G1降低了并发标记的成本,同时通过并行复制的方式对部分堆内存进行整理 ZGC C4 ShenandoahGC 进一步降低并发标记 STW 时间,通过并发复制将对象转移时的暂停时间最小化 并发算法可以正常执行的前提是 垃圾回收的速度大于对象的分配速度 并发算法需要更大的堆空间,同时需要预留部分空间

上一篇     下一篇
Java NIO 内存映射文件

java随机数生成器

ZGC指南

CMS G1 ZGC 堆内存区别

ZGC特性简介

ZGC垃圾回收触发机制