7.5 并发转移算法
转移就是将标记之后的存活对象使用复制算法转移到另外一个页面。
(1)并发转移准备
分析最有价值 GC 分页<无 STW> 。
(2)初始转移
转移初始标记的存活对象同时做对象重定位<有 STW>。
对应的初始标记,只转移对象 A。并且指针颜色发生了变化,完成了重定位,变成蓝色指针 remapped。初始转移指针直接就修改了,而且初始转移是 STW 的,因此不存在新旧地址的说法。
(3)并发转移
对转移并发标记的存活对象做转移<无STW>,由于该阶段业务线程是运行的,因此需要通过转发表来记录新旧地址的映射。转移对象和转发表记录的插入是需要做原子操作的。
(4)复制完之后,清除小页面A
(5)进入下一次垃圾回收周期
假设在两次 GC 中间创建了一个对象 E。新创建的对象的指针是蓝色的。
(6)第二次初始标记,使用M1(红色)
(7)第二次并发标记,上一次并发转移的对象需要做重定位
也就是对象转移和删除转发表记录做原子操作。A 指向 B 的指针做修正,同时 A 指向E的指针也要变成 M1 红色。
八、ZGC的触发机制
(1)预热规则
JVM 启动预热,如果从来没有发生过 GC,则在堆内存使用超过 10%、20%、30% 时,分别触发一次 GC,以收集 GC 数据。
(2)基于分配速率的自适应算法
最主要的 GC 触发方式(默认方式),其算法原理可简单描述为”ZGC 根据近期的对象分配速率以及 GC 时间,计算出当内存占用达到什么阈值时触发下一次 GC”。通过 ZAllocationSpikeTolerance 参数控制阈值大小,该参数默认 2,数值越大,越早触发 GC。日志中关键字是“Allocation Rate”。
(3)基于固定时间间隔
通过 ZCollectionInterval 控制,适合应对突增流量场景。流量平稳变化时,自适应算法可能在堆使用率达到 95% 以上才触发 GC。流量突增时,自适应算法触发的时机可能会过晚,导致部分线程阻塞。我们通过调整此参数解决流量突增场景的问题,比如定时活动、秒杀等场景。
(4)主动触发规则
类似于固定间隔规则,但时间间隔不固定,是 ZGC 自行算出来的时机,我们的服务因为已经加了基于固定时间间隔的触发机制,所以通过 -ZProactive 参数将该功能关闭,以免 GC 频繁,影响服务可用性。
(5)阻塞内存分配请求触发
当垃圾来不及回收,垃圾将堆占满时,会导致部分线程阻塞。我们应当避免出现这种触发方式。日志中关键字是“Allocation Stall”。
(6)外部触发
代码中显式调用 System.gc() 触发。日志中关键字是“System.gc()”。
(7)元数据分配触发
元数据区不足时导致,一般不需要关注。日志中关键字是“Metadata GC Threshold”。
九、ZGC 参数设置
- 堆大小 :Xmx。当分配速率过高,超过回收速率,造成堆内存不够时,会触发 Allocation Stall,这类 Stall 会减缓当前的用户线程。因此,当我们在 GC 日志中看到 Allocation Stall,通常可以认为堆空间偏小或者 concurrent gc threads 数偏小。
- GC 触发时机 :ZAllocationSpikeTolerance, ZCollectionInterval。ZAllocationSpikeTolerance 用来估算当前的堆内存分配速率,在当前剩余的堆内存下,ZAllocationSpikeTolerance 越大,估算的达到 OOM 的时间越快,ZGC 就会更早地进行触发 GC。ZCollectionInterval 用来指定 GC 发生的间隔,以秒为单位触发 GC。
- GC 线程 :ParallelGCThreads, ConcGCThreads。ParallelGCThreads 是设置 STW 任务的 GC 线程数目,默认为 CPU 个数的 60%;ConcGCThreads 是并发阶段 GC 线程的数目,默认为 CPU 个数的 12.5%。增加 GC 线程数目,可以加快 GC 完成任务,减少各个阶段的时间,但也会增加 CPU 的抢占开销,可根据生产情况调整。