https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/share/gc
3.2 ZGC的特性
并发
由于停顿时间小于10ms,显而易见的,回收周期绝大部分逻辑将于Mutator并发执行。
基于区域
与G1类似,JAVA Heap被划分为多个Region,但ZGC中Region大小并不唯一。
压缩算法
为了避免内存碎片,需要在回收周期中进行压缩。
支持NUMA
作为下一代垃圾回收算法,支持NUMA是必须的。
使用colored pointers
CMS和G1标记的是对象,而ZGC标记的是对象指针。
开始标记(STW),标记GC roots
并发的遍历堆中对象,描绘出引用关系
结束标记(STW),记录活动对象和需要回收的对象
并发的relocate准备阶段,弱引用和软引用的处理,类的卸载,relocate集合的选择等
开始relocate(STW),relocate GC roots
并发relocate,这是GC线程和Mutator线程的读屏障都可进行relocate
缺点
不支持分代回收
由于ZGC尚出于实验阶段,且分代代码代码实现较困难,目前ZGC并没有区分新生代和老年代。
因为没有分代,每次回收都会试图并发标记整个JAVA Heap,并发标记过程可能达到分钟级,如果对象分配速率很高的话,在此期间就可能分配出大量对象,导致ZGC处理困难。
后续ZGC会推出分代机制或Thread Local GC修复此问题。
不支持指针压缩
由于引入了colored pointer机制,不再支持指针压缩。而指针压缩对于32GB以下堆有着显著的性能和空间利用率提升。
支持操作系统很少
仅支持Linux/x64,JDK 13中将支持Linux/AArch64
ZGC提供四种策略,其中一种满足条件即触发回收:
rule_timer,定时策略,距离上次GC时间超过interval即触发GC
rule_warmup,VM启动后,如果从来没有发生过GC,则在堆内存使用超过10%、20%、30%时,分别触发一次GC,以收集GC数据
rule_allocation_rate,根据对象分配速率决定是否GC
rule_proactive,主动控制策略,根据距离上次GC增长的堆空间和距离上次GC的时间判断是否触发GC
如果对象分配过快,以至于以上四种策略均无法及时回收对象,则在到达阈值后,STW并行回收。
GC触发策略
如果VM启动后,从来发生过GC,则不使用rule_allocation_rate
ZGC分配速率的计算与G1不同,采用的是正态分布,置信度为99.9%时,最大内存分配速率为((ZStatAllocRate::avg() * 1) + (ZStatAllocRate::avg_sd() * 3.290527))
ZAllocationSpikeTolerance是个修正参数,默认2,加入该修正系数后,置信度远大于99.9%,计算公式为((ZStatAllocRate::avg() * ZAllocationSpikeTolerance) + (ZStatAllocRate::avg_sd() * 3.290527))
根据最大分配速率,可以计算出到达OOM的剩余时间
同样根据正态分布,取置信度99.9%,计算出最大GC持续时间
当time_until_oom - max_duration_of_gc - sample_interval小于0时,即触发GC
先判断JVM参数ZProactive(默认true),如果是false则不使用rule_proactive
如果VM还没有预热,则不使用rule_proactive
如果距离上次GC,堆内存占用增长小于10%且小于5分钟,则不使用rule_proactive
如果距离上次GC时间超过最大预测GC时间乘49,则触发GC
ZGC与传统GC不同,标记阶段标记的是指针(colored pointer),而非传统GC算法中的标记对象。ZGC借助内存映射,将多个地址映射到同一个内存文件描述符上,使得ZGC回收周期各阶段能够使用不同的地址访问同一对象
内存映射之前需要创建文件描述符,名称为java_heap
2.3 分级管理
与操作系统类型,为了便于管理,ZGC也定义了两级结构:虚拟内存和物理内存,分别由ZVirtualMemoryManager和ZPhysicalMemoryManager管理。
ZGC的物理内存并不是通常意义上的物理内存,而仅表示mutator使用的Java heap内存。因此仍然是用户空间虚拟内存,仅为了与Mark0、Mark1、Remapped视图区分。本文中的物理内存,如无特殊说明,特指ZGC中的物理内存。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
与G1的分区类似,ZGC将堆划分成Page进行管理,不同的是ZGC中Page大小不是固定的,而是分为三种Small、Medium、Large三类。
ZGC有3种不同的页面类型:小型(2MB),中型(32MB)和大型(2MB的倍数)。
large page分配时,需要按small page size对齐,因此large page的size一定是2M的整数倍。
当对象size小于等于256K时(2M / 8),在small page分配
当对象size小于等于4M时(32M / 8),在medium page分配
当对象size大于4M时,在large page分配
根据JVM参数ZStallOnOutOfMemory判断,true则调用alloc_page_blocking,false则调用alloc_page_nonblocking分配page
分配成功后,判断该page是否已经map,如否,则map到marked0、marked1、remapped虚拟内存空间
ZGC的对象分配流程大体与G1类似。分配流程图在JVM G1 源码分析(二)- 对象的堆空间分配内。
主要代码在src/hotspot/share/gc/shared目录下,为多个GC算法的共用代码。
ZGC定制逻辑由gc shared代码调用ZCollectedHeap、ZHeap等ZGC派生类实现。
2.1 allocate_new_tlab
当线程的TLAB可用内存不足时,MemAllocator的allocate_inside_tlab_slow会调用ZCollectedHeap的allocate_new_tlab申请新的TLAB。
分配前,先对象字节对齐,默认8字节对齐
调用ZHeap的alloc_tlab进行分配
alloc_tlab在校验size合法性后,调用ZObjectAllocator的alloc_object进行分配。
由于TLAB max size是2MB,会调用alloc_small_object进行分配。
2.2 mem_allocate
当TLAB分配失败时,MemAllocator的allocate_outside_tlab会调用ZCollectedHeap的mem_allocate进行慢分配。
size需要对象对齐,默认8字节对齐
mem_allocate调用ZHeap的alloc_object,进行对象分配。
根据需要分配的size大小,决定调用哪个函数进行分配。
alloc_object_atomic没有锁,而是通过自旋+CAS实现并发控制,自旋使用CAS分配对象,直到成功或Page没有足够的空间
大对象分配略有不同
GC回收周期包括如下11个子阶段:
phase 1:初始标记,需要STW
phase 2:并发标记
phase 3:标记结束,需要STW
phase 4:并发处理软引用、弱引用
phase 5:并发重置Relocation Set
phase 6:并发销毁可回收页
phase 7:内存验证
phase 8:并发选择Relocation Set
phase 9:并发准备Relocation Set
phase 10:开始Relocate,STW
phase 11:并发Relocate
ZGC使用ZMessagePort类传递消息,ZMessagePort内部使用了ZList队列
同步消息逻辑如下:
消息消费者负责消费队列中的消息,如果是异步消息,则直接读取类变量_message。
2.3 开始gc cycle
ZDriver启动一个线程,死循环判断是否应该启动gc cycle
start_gc_cycle调用ZMessagePort的receive方法等待启动请求
调用run_gc_cycle方法,执行GC
执行GC后,调用end_gc_cycle,ACK启动请求