走进ZGC

141 阅读6分钟

概要

  • 看了下最近JDK的几次迭代,主要都在优化ZGC,未来ZGC成为主流的垃圾收集器也是早晚的事,今天先浅学下ZGC的相关知识。
  • 在垃圾回收器的历史上,CMS是第一款实现了用户线程和垃圾收集线程并发收集的垃圾收集器,G1第一次提出优先列表的概念,每次优先回收价值大的区域,不再是像之前的垃圾收集器一样,每次回收全部的堆内存,而是边使用边收集,这样也大大缩短了暂停用户线程的时间。而ZGC相比于G1,有更大的突破,下面来介绍一下。

ZGC目标

5a279d7e5497b8dade583f9a18243002.png

  • ZGC在设计之初便定义了几大目标:
  1. 支持TB级别的堆;
  2. 最大GC停顿时间不超过10ms;
  3. 奠定未来GC特性的基础;
  4. 最坏的情况下应用程序的吞吐量下降15%。
  • 官方还提到ZGC的垃圾收集的停顿时间不会随着堆的大小或者是活跃对象的大小而增大,也就是说GB级别和TB级别的收集时间都一样。

ZGC内存布局

  • ZGC是一款基于Region内存布局的,没有分代的概念。ZGC的Region具有小、中、大三类容量,根据对象的大小决定对象在哪类Region分配:
  1. 小型Region:容量固定为2M,用于放置小于256K的小对象;
  2. 中型Region:容量固定为32M,用于放置大于256K但小于4M的对象;
  3. 大型Region:容量不固定,可以动态变化,但必须是2M的整数倍,用于放置4M以上的大对象,每个大型Region中只会存放一个大对象。

ebd2550f10f141978d3c5c57641a86a8.png

ZGC核心技术

  • 在gc时,存活的对象会被复制到另一块Region中,对象的内存地址发生了变化,ZGC使用对象重定位的技术,将对象老地址的指针调整到对象新地址上。
  • 例如:堆中有一个对象,指针为Good Color,在标记阶段,该对象是可达的活跃对象,因此指针会被标记为Bad Color(颜色指针)。在转移阶段,Bad Color的对象会被转移到新的Region中。转移结束后,对象持有的指针还是指向原来的老地址。当应用线程读取到这个指针时,会去一个转发表中找到对象最新的地址(读屏障),然后修正原来的地址。
  • 以上是ZGC的惰性对象重定位的大致流程,这也是ZGC在筛选回收阶段不需要STW的原因,但是G1会STW,因为如果不STW的话,应用程序就会对对象的老地址进行操作,造成不可预测的错误。

颜色指针

ZGC的核心设计之一,以前的垃圾回收器的GC信息,都保存在对象头的MarkWord中,而ZGC保存在对象的指针中。因为追踪式收集算法的标记阶段就是看有没有引用,所以可以只和指针打交道而不管指针所引用的对象本身。比如:对象标记过程就是进行三色标记,这些标记本质上只和对象引用有关,和对象本身无关。

640.png

  • Finalizable:表示这个对象只能通过finalizer才能访问;
  • Remapped:表示是否进入了重分配集;
  • 另外2位Mark0、Mark1是辅助GC标记的,每个周期开始前,会交换使用的标志位,一个GC周期使用Mark0,一个GC周期使用Mark1。
  • 因为对象指针必须是64位,ZGC无法支持32位操作系统,也无法支持指针压缩。
  • 颜色指针的核心优势:
  1. 一旦某个Region的存活对象被移走后,这个Region就能立刻释放和重用,不必等待整个堆中所有指向该Region的引用被修正后才能清理;
  2. 大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障
  3. 具备强大的扩展性,它可以作为一种可扩展的存储结构来记录与对象标记、重定位过程相关的数据。

读屏障

  • 在标记和移动对象的阶段,每次从堆里对象的引用类型中读取一个指针的时候,都需加上一个load barriers(读屏障),在应用程序读取旧的对象时,这个屏障会把读出的指针更新到对象的新地址上(从转发表中查询对象的新地址),并且把堆里的这个指针“修正”到原本的字段里。应用程序永远持有更新后的有效指针,而且不需要STW。
  • JVM如何判断指针被移动过?利用颜色指针,如果是Bad Color,则需要修正指针,如果是Good Color,正常往下执行。

ZGC执行过程

1f8517642e4e496c9bbeb97a5e7a7ca7.png

  • 并发标记:与G1一样,遍历对象图做可达性分析的阶段。它的初始标记和最终标记也会出现短暂的停顿。与G1不同,ZGC的标记是在指针上而不是在对象上。
  • 并发预备重分配:根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集。
  • 并发重分配:是ZGC执行过程中的核心阶段。把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,记录从旧对象到新对象的转向关系(读屏障使用),ZGC将这种行为成为指针的“自愈”能力。
  • 并发重映射:修正整个堆中指向重分配集中旧对象的所有引用,因为ZGC存在自愈,所以该步骤不是很急切。

总结

  • ZGC的优点显而易见:低停顿、高吞吐量、收集过程中额外耗费的内存小。主要的缺点是会产生浮动垃圾:虽然ZGC的停顿时间在10ms以下,但是ZGC的执行时间还是远远大于这个时间,所以在执行过程中产生的对象很难被当前GC回收,只能在下次GC才回收。于是会产生浮动垃圾。而且ZGC没有分代的概念,每次都要扫描全堆,有些“朝生夕死”的对象没能被及时回收。
  • 所以ZGC适合在超大堆上使用,业务上如果对停顿有要求,也可以选用。