公众号「后端进阶」 每一步成长都想与你分享

令人惊叹的ZGC

2018-09-28
zch
 

随着万众瞩目的 JDK11 的发布,ZGC 也随着 JDK11 面世了,这是一个处于实验阶段的,可扩展的低延迟垃圾回收器,为什么说他令人惊叹,我们都知道 发生 GC 时,会发生 stop the world,也就是全部线程停止运行,官方承诺 ZGC 将 GC 时长控制在10ms以内,而且还不会随着堆的大小发生变化,这是什么概念,你要知道上一代的 G1,通常都会在100ms以上,你就知道 ZGC 有多牛了。

特性

据官方说:ZGC项目需要完成的目标是:控制Java的垃圾回收时长在10ms以内,绝对不超过10ms,并且使用了该垃圾回收策略之后,吞吐对比当前Java缺省的垃圾回收策略G1,下降不超过15%

总的来说,ZGC主要有以下目标:

  1. GC 停顿时间不超过10ms
  2. 停顿时间不随heap大小或存活对象大小增大而增大
  3. 可以处理从几百兆到几T的内存大小

我们先来看一张对比图:

zgc

再从官方得出的一个测试:

128G 的堆,复合模式下的性能,看 GC 停顿时间:

ZGC
                avg: 1.091ms (+/-0.215ms)
    95th percentile: 1.380ms
    99th percentile: 1.512ms
  99.9th percentile: 1.663ms
 99.99th percentile: 1.681ms
                max: 1.681ms

G1
                avg: 156.806ms (+/-71.126ms)
    95th percentile: 316.672ms
    99th percentile: 428.095ms
  99.9th percentile: 543.846ms
 99.99th percentile: 543.846ms
                max: 543.846ms

这就够惊叹了,竟然在 128g 的堆内存中,gc 停顿不超过 2ms,远远低于官方保守的 10ms!

它为什么这么快?其实有原因的,我从官方描述里总结了以下几点:

  • stop the world 只发生在 root 扫描的时候,之后就不会再发生了,所以无论多大的堆,对 GC 停顿时间的影响微乎其微
  • 并发执行,意味着从 root 扫描有短暂的 stw,之后的 GC 操作都不会再停顿了
  • ZGC 中堆内存也被分成大量的区域,它们被称为 page
  • 基于标记整理算法,并使用有色纸针(使用 colored oops)、加载屏障(Load Barrier),这两个也是 ZGC 最重要的技术了,以下是从官网摘录ZGC阶段:
    1. GC循环从标记阶段开始,递归标记所有可达对象,标记阶段结束时,ZGC可以知道哪些对象仍然存在哪些是垃圾。ZGC将结果存储在每一页的位图(称为live map)中。
    2. 在标记阶段,应用线程中的load barrier将未标记的引用压入线程本地的标记缓冲区,一旦缓冲区满,GC线程会拿到缓冲区的所有权,并且递归遍历此缓冲区所有可达对象
    3. 标记阶段后,ZGC需要迁移relocate集中的所有对象。relocate集是一组页面集合,包含了根据某些标准(例如那些包含最多垃圾对象的页面)确定的需要迁移的页面。对象由GC线程或者应用线程迁移(通过load barrier)。ZGC为每个relocate集中的页面分配了转发表。转发表是一个哈希映射,它存储一个对象已被迁移到的地址(如果该对象已经被迁移)
    4. GC线程遍历relocate集的活动对象,并迁移尚未迁移的所有对象。有时候会发生应用线程和GC线程同时试图迁移同一个对象,在这种情况下,ZGC使用CAS操作来确定胜利者。
    5. 一旦GC线程完成了relocate集的处理,迁移阶段就完成了。虽然这时所有对象都已迁移,但是旧地引用址仍然有可能被使用,仍然需要通过转发表重新映射(remapping)。然后通过load barrier或者等到下一个标记循环修复这些引用,这也解释了为什么对象引用中有两个标记位(marked0和marked1)。标记阶段交替使用在marked0和marked1位。*
    6. 从堆中读取引用时,ZGC需要一个所谓的load barrier(也称为read-barrier)。每次Java程序访问对象字段时,ZGC都会执行load barrier的代码逻辑,例如obj.field。访问原始类型的字段不需要屏障,例如obj.anInt或obj.anDouble。ZGC不使用存储/写入障碍obj.field = someValu,如标记整理章节所说,根据GC当前所处的阶段,如果尚未标记或迁移引用,则屏障会标记对象或迁移它。*

总结 ZGC 的 STW 为什么这么短的原因就是:仅root扫描时STW,其他标记、清理、迁移阶段,均通过colored oops和load-barrier配合使用,并发执行。

使用参数:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

因为ZGC还处于实验阶段,所以需要通过JVM参数UnlockExperimentalVMOptions 来解锁这个特性。


微信公众号「Java科代表」

Content