ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,目前处于试验阶段,它的优势包括:
- 停顿时间不超过10ms,可以在KB~TB的工作范围下进行垃圾回收;
- 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
- 支持8MB~4TB级别的堆(未来支持16TB)
为什么要引入ZGC
众所周知在一些高性能低延迟的应用中,GC往往是应用的瓶颈,因为在GC期间会发生STW(Stop The World),当STW时,所有应用线程停止活动,等待GC停顿结束。在一些对响应时间比较苛刻的系统中,GC往往是非常难优化的一点,这也是为什么垃圾收集器在不断演进的一个重要原因。
ZGC保证,不管在什么情况下,延迟不会超过10毫秒。
The Z Garbage Collector, also known as ZGC, is a scalable low latency garbage collector designed to meet the following goals:
- Pause times do not exceed 10ms
- Pause times do not increase with the heap or live-set size
- Handle heaps ranging from a few hundred megabytes to multi terabytes in size
现对于CMS和G1的GC过程以及停顿时间的瓶颈,全局并发的ZGC(标记、转移和重定位阶段几乎都是并发的)显然是一个更好的选择。
ZGC的特点
ZGC最典型的特性是它是一款并发(concurrent)的GC,其它的特性如下:
-
它可以标记内存,复制和迁移(relocate)内存,所有的操作都是并发的,同时它有一个并发的引用处理器
-
其它的垃圾收集器都是使用store barriers,ZGC使用load barriers,用于跟踪内存
- lock->unlock->read->load 读内存
- use->assign->store->write 写内存
-
ZGC可以更加灵活的配置大小和策略,相比于G1,它可以更好的处理非常大(very large)对象的释放
-
ZGC只有一代,没有新生代,老年代什么的,但是ZGC可以支持局部压缩,在内存恢复和迁移(reclaim and relocate)时,ZGC仍然有很高的性能
-
ZGC依赖NUMA-aware(非均衡存储器访问),需要我们的内存支持这种特点
ZGC堆内存布局
-
与G1一样,ZGC也采用基于Region的堆内存布局
-
ZGC的Region具有动态性
-
动态的创建和销毁
-
动态的Region容量大小
大小分类: -
小型Region(Small Region):固定大小2MB,存放小于256KB的小对象
-
中型Region(Medium Region):固定大小32MB,存放大于256KB小于4MB的对象
-
大型Region(Large Region):大小不固定,可以动态变化,但必须是2MB的整数倍,用于放大于4MB的大对象,每个大型Region只会放一个大对象,所以实际容量可能会小于中型Region,最小到4MB。大型Region在ZGC实现中不会被重分配,因为复制一个大对象代价太高。
ZGC的着色指针技术
着色指针是一种直接将少量额外的信息存储在指针上的技术。目前在Linux下64位的操作系统中高18位是不能用来寻址的,但是剩余的46为却可以支持64T的空间,到目前为止我们几乎还用不到这么多内存。于是ZGC将46位中的高4位取出,用来存储4个标志位,剩余的42位可以支持4TB(2的42次幂)的内存,也直接导致ZGC可以管理的内存不超过4TB,如图所示:
- Marked0/marked1: 判断对象是否已标记
- Remapped: 判断应用是否已指向新的地址
- Finalizable: 判断对象是否只能被Finalizer访问
这几个bits在不同的状态也就代表这个引用的不同颜色
对象标记过程就是打个三色标记,这些标记本质上只和对象引用有关,和对象本身无关。某个对象只有它的引用关系才能决定它的存活。
ZGC使用了内存多重映射(Multi-Mapping)将多个不同的虚拟内存地址映射到同一个物理内存地址上,这是一种多对一映射。因为染色指针只是重新定义内存中某些指针的其中几位,OS又不支持,OS只会把整个指针当做一个内存地址来对待,只是它自己瞎想,为了解决这个问题,使用了现代处理器的虚拟内存映射技术。
ZGC的读屏障技术
读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。
ZGC回收流程
我们将上图进行拆解来理解
- 初始标记(STW)
停止用户线程,标记GC Root对象. 1、2、4被标记为存活对象。
- 并发标记
并发递归从GC Root开始遍历可达对象,5、8被标记为存活对象。
- 移动对象
对比发现3、6、7是过期的对象,中间灰色的Region需要被清理压缩,所以将4、5、8移动到右边空的Region,移动过程中有个forward table
记录这种转变。
- 修正指针
由于4、5、8发生了移动,所以需要修正。
总结
- ZGC作为下一代垃圾回收器,性能非常优秀。ZGC垃圾回收过程几乎全部是并发,实际STW停顿时间极短,不到10ms。这得益于其采用的着色指针和读屏障技术。
- ZGC不是“银弹”,需要根据服务的具体特点进行调优,并且需要支持NUMA-aware(非均衡存储器访问),需要我们的内存支持这种特点。所以还是要根据公司业务的需求和实际情况来权衡。
参考链接
- https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html
- https://www.baeldung.com/jvm-zgc-garbage-collector
- https://hub.packtpub.com/getting-started-with-z-garbage-collectorzgc-in-java-11-tutorial/