Java ZGC垃圾收集器(算法及回收原理详解)

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC作为Java推出的新一代的JVM垃圾收集器,是一款非常优秀的垃圾收集器,下面我会全面详解ZGC@mikechen

什么是ZGC?

ZGC也称为(The Z Garbage Collector),是JDK 11中推出的一款追求极致低延迟的性质的JVM垃圾收集器

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC是最近由Oracle为OpenJDK开源的新JVM垃圾收集器,旨在满足以下目标:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

  1. 停顿时间(STW)不超过10ms
  2. 停顿时间不会随着堆的大小,或者活跃对象的大小而增加
  3. 支持8MB~4TB级别的堆,未来支持16TB

 

ZGC的特征

  • ZGC 具有以下特征:
  • 并发
  • 基于 region
  • 压缩
  • NUMA 感知
  • 使用彩色指针
  • 使用负载屏障

 

ZGC的原理

1.ZGC内存布局

讲到ZGC的原理,先从ZGC 的内存布局谈起。

与 Shenandoah 和 G1一样,ZGC 也采用基于 Region 的堆内存布局,但与它们不同的是 , ZGC 的 Region 具 有 动 态 性 :

  • 动态创建
  • 动态销毁
  • 以及动态的区域容量大小

ZGC 的 Region 可以具有大、中、小三类容量,如下图所示:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

  • Small Region:2MB,主要用于放置小于 256 KB 的小对象。
  • Medium Region:32MB,主要用于放置大于等于 256 KB 小于 4 MB 的对象。
  • Large Region:N * 2MB,这个类型的 Region 是可以动态变化的,不过必须是 2MB 的整数倍,最小支持 4 MB。每个 Large Region 只放置一个大对象,并且是不会被重分配的。

 

2.ZGC的垃圾回收机制

与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进。

ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。

ZGC垃圾回收周期如下图所示:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC只有三个STW阶段:初始标记再标记初始转移

其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短,再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。

ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加,与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。

 

3.染色指针

ZGC 出现之前, GC 信息保存在对象头的 Mark Word 中,比如 64 位的 JVM,对象头的 Mark Word 中保存的信息如下图:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

前 62位保存了 GC 信息,最后两位保存了锁标志。

ZGC 的一大创举是将 GC 信息保存在了染色指针上,是ZGC 的核心设计之一。

在 64 位 JVM 中,对象指针是 64 位,如下图:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

 

4.多重映射寻址

ZGC中管理物理内存的基本单位是segment,segment默认与small page size一样,都是2MB。

引入segment是为了避免频繁的申请和释放内存的系统调用,一次申请2MB,当segment空闲时,将加入空闲列表,等待之后重复使用。

ZGC为了能高效、灵活地管理内存,实现了两级内存管理:虚拟内存和物理内存,并且实现了物理内存和虚拟内存的映射关系,这和操作系统中虚拟地址和物理地址设计思路基本一致。

本质就是使用 mmap 把不同的虚拟内存地址映射到同一个物理内存地址上,如下图:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

当应用程序创建对象时,首先在堆空间申请一个虚拟地址,ZGC同时会为该对象在Marked0、Marked1和Remapped三个视图空间分别申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址,如下图所示:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC 为了更灵活高效地管理内存,使用了内存多重映射,把同一块儿物理内存映射为 Marked0、Marked1 和 Remapped 三个虚拟内存。

当应用程序创建对象时,会在堆上申请一个虚拟地址,这时 ZGC 会为这个对象在 Marked0、Marked1 和 Remapped 这三个视图空间分别申请一个虚拟地址,这三个虚拟地址映射到同一个物理地址。

Marked0、Marked1 和 Remapped 这三个虚拟内存作为 ZGC 的三个视图空间,在同一个时间点内只能有一个有效。ZGC 就是通过这三个视图空间的切换,来完成并发的垃圾回收。

 

5.读屏障

读屏障也是是ZGC 的核心设计之一,读屏障是JVM向应用代码插入一小段代码的技术。

当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。

读屏障示例:

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC中读屏障的代码作用:在对象标记和转移过程中,用于确定对象的引用地址是否满足条件,并作出相应动作。

ZGC并发处理演示

接下来详细介绍ZGC一次垃圾回收周期中地址视图的切换过程:

  • 初始化:ZGC初始化之后,整个内存空间的地址视图被设置为Remapped。程序正常运行,在内存中分配对象,满足一定条件后垃圾回收启动,此时进入标记阶段。
  • 并发标记阶段:第一次进入标记阶段时视图为M0,如果对象被GC标记线程或者应用线程访问过,那么就将对象的地址视图从Remapped调整为M0。所以,在标记阶段结束之后,对象的地址要么是M0视图,要么是Remapped。如果对象的地址是M0视图,那么说明对象是活跃的;如果对象的地址是Remapped视图,说明对象是不活跃的。
  • 并发转移阶段:标记结束后就进入转移阶段,此时地址视图再次被设置为Remapped。如果对象被GC转移线程或者应用线程访问过,那么就将对象的地址视图从M0调整为Remapped。

其实,在标记阶段存在两个地址视图M0和M1,上面的过程显示只用了一个地址视图。

之所以设计成两个,是为了区别前一次标记和当前标记。即第二次进入并发标记阶段后,地址视图调整为M1,而非M0。

着色指针和读屏障技术不仅应用在并发转移阶段,还应用在并发标记阶段:将对象设置为已标记,传统的垃圾回收器需要进行一次内存访问,并将对象存活信息放在对象头中,而在ZGC中,只需要设置指针地址的第42~45位即可,并且因为是寄存器访问,所以速度比访问内存更快。

Java ZGC垃圾收集器(算法及回收原理详解)-mikechen

ZGC总结

ZGC作为下一代垃圾回收器,性能非常优秀,ZGC垃圾回收过程几乎全部是并发,实际STW停顿时间极短,不到10ms,这得益于其采用的着色指针和读屏障技术。

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法