什么是 ZGC
ZGC 是一种可扩展的低延迟垃圾回收器,GC 过程中 STW 不超过一毫秒,适合需要低延迟的应用,支持几百 MB 到16 TB 的堆大小,并且对大小对 STW 时间没有影响
配置 ZGC
shell
# 分代 ZGC
-XX:+UseZGC -XX:+ZGenerational
# 非分代 ZGC -> 不区分年轻代、老年代
-XX:+UseZGC
配置参数
ZGC 在设计上做到了自适应,可以根据运行情况自动配置参数,让用户手动配置的参数最少化
无需设置
- 自动设置年轻代大小
- 自动晋升阈值
- 自动并行线程数
可选设置
-Xmx: 设置最大堆内存
-XX:SoftMaxHeapSize: ZGC 尽量保证堆内存小于该值,当堆内存靠近该值后会尽快进行垃圾回收,但是仍然有可能超过该值
内存划分
ZGC 将内存划分了很多区域,这些区域称之为 ZPAGE
- 小区域,2M,只保存 256 KB 内的对象
- 中区域,32 M,保存 256 KB ~ 4M 的对象
- 大区域,只保存一个大于 4M 的对象
回收过程
- 初始标记,STW,标记 GC Roots 直连
- 并发标记,遍历所有对象,标记可以达到的对象,
用户线程也会帮忙标记 - 标记结束,STW
- 并发处理,处理软、弱等引用、清理空区域(ZPAGE)
- 转移开始,STW,转移 GC Roots 直连对象
- 并发转移、并发重映射:将剩余存活对象(非 GC Roots 直连)转移到新区域中,并重新建立引用关系

为什么 G1 转移过程需要 STW
G1 在转移过程中,修改了对象的引用之前执行了 A.c.count = 2,此时更改的是转移前对象的属性
之后修改对象引用,会发现新引用的对象的 A.c.count = 1

ZGC 并发转移的原理
ZGC 通过读屏障实现转移后对象的获取,获取一个对象时,会触发读后的屏障指令,如果对象指向的不是转移后的对象,用户现场会将引用指向转移后的对象
着色指针
访问对象引用其实是访问对象的内存地址,64 位操作系统中, 8 字节的内存地址会有多余字段,着色指针就利用了多余字段存储状态信息

染色指针将 8 字节的内存地址拆分为了三部分:
- 最低的 44 位,表示对象的内存地址
- 中间 4 位是颜色位,每一位只能存放 0 或者 1,并且同一时间只有其中一位是 1
- 终结位,只能通过终结器访问
- 重映射位,转移完之后,对象的引用关系已经变更
- Marked0 和 Marked1,标记可达对象
- 剩余 16 位没有使用

初始标记
STW,将 GC Roots 直连对象的 Marked0 置为 1,同时将直连对象标记为存活

并发标记
遍历所有对象,标记每一个对象是否存活,用户线程使用读屏障,如果发现对象没有完成标记也会帮忙完成标记

并发处理
选择需要转移的 ZPAGE,并创建转移表,用于记录转移前对象和转移后对象地址
转移开始
转移 GC Roots 直连对象,并将转移的对象 Remmaped 值设置成 1,避免重复进行判断

并发转移
将剩余的存活对象转移到新 Page 中,转移后将对象的新旧地址存储到映射表中,并将新对象的 Remapped 设置为 1

转移完成后将转移前的 ZPage 进行清空,保留映射表
此时如果线程访问了 4~ 的 5 对象但是 5 已经被转移到 5~ 了,访问线程就会通过转移映射表将指向 5 的指针改为指向 5~,并将颜色改为
并发转移结束之后,这一轮的垃圾回收就结束了,ZGC 不会处理本次转移的所有指针的重映射,等到下一次 GC 时的并发标记阶段进行处理