ZGC在jdk11只支持Linux版本,4TB的内存,STW时间控制在10ms内;jdk16已经支持16TB的内存,STW时间不超过1ms,下面主要针对jdk11版本的详解
一、堆内存结构
ZGC堆内存分为三种类型的页面即小页面(空间大小2M,存放小于256KB的对象)、中页面(空间大小32M,存放大于256KB小于4M的对象)、大页面(空间大小>32M,存放大于4M的对象)
1、什么这么设计?
标准大页(huge page)是Linux Kernel 2.6引入的,目的是通过使用大页内存来取代传统的4KB内存页面,以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能。
Huge pages 有两种格式大小: 2MB 和 1GB , 2MB 页块大小适合用于 GB 大小的内存, 1GB 页块大小适合用于 TB 级别的内存; 2MB 是默认的页大小。
所以ZGC这么设置也是为了适应现代硬件架构的发展,提升性能。
2、ZGC支持NUMA
在过去,对于X86架构的计算机,内存控制器还没有整合进CPU,所有对内存的访问都需要通过北桥芯片来完成。X86系统中的所有内存都可以通过CPU进行同等访问。任何CPU访问任何内存的速度是一致的,不必考虑不同内存地址之间的差异,这称为“统一内存访问”(Uniform Memory Access,UMA)。UMA系统的架构示意图如图所示。
在UMA中,各处理器与内存单元通过互联总线进行连接,各个CPU之间没有主从关系。之后的X86平台经历了一场从“拼频率”到“拼核心数”的转变,越来越多的核心被尽可能地塞进了同一块芯片上,各个核心对于内存带宽的争抢访问成为瓶颈,所以人们希望能够把CPU和内存集成在一个单元上(称Socket),这就是非统一内存访问(Non-Uniform Memory Access,NUMA)。很明显,在NUMA下,CPU访问本地存储器的速度比访问非本地存储器快一些。下图所示是支持NUMA处理器架构示意图。
ZGC是支持NUMA的,在进行小页面分配时会优先从本地内存分配,当不能分配时才会从远端的内存分配。对于中页面和大页面的分配,ZGC并没有要求从本地内存分配,而是直接交给操作系统,由操作系统找到一块能满足ZGC页面的空间。ZGC这样设计的目的在于,对于小页面,存放的都是小对象,从本地内存分配速度很快,且不会造成内存使用的不平衡,而中页面和大页面因为需要的空间大,如果也优先从本地内存分配,极易造成内存使用不均衡,反而影响性能。
二、颜色指针
2的42次方是4TB,如果要支持4TB内存就需要占用低42位,借高几位做gc标记和重定位生成不同的64位虚拟地址,也就是说只能运行在64位机器上,并且不支持指针压缩
三、回收过程
回收过程主要分为三个阶段标记(mark)、转移(relocate)、重定位(remap)
1、标记
- 初始标记:GC Roots的直接关联的对象进行标记M0,需要STW
- 并发标记:GC Roots关联的对象继续往下根据根可达算法继续查找,这个阶段与业务线程同时进行,可能会出现漏标的情况
- 再标记:这个阶段修正漏标的问题,通过SATB算法解决,需要STW
2、转移
- 并发转移准备:计算哪些区域垃圾最多,如果一个页面全是垃圾,那就做清理操作
- 初始转移:初始标记所标记的对象就是初始转移要转移的对象,即GC Roots直接关联的对象,复制同时做重定位,需要STW
- 并发转移:并发标记所标记的对象就是并发转移要转移的对象,即GC Roots直接关联的对象的下级的对象,复制;这个阶段不需要STW,如果初始转移的对象的下级对象还没有做转移,ZGC里面有个转发表,记录并发标记对象转移后新旧地址映射,转移和插转发表是原子操作
3、重定位
并发转移的重定位是第二次垃圾回收做重定位
- 初始标记:上次转移GC Roots直接引用的对象内存地址做m1标识
- 并发标记上次并发转移对象的重定位,删转发表和修正指针的原子操作也做m2标识
四、ZGC中的读屏障
在第一次gc和第二次gc中间有访问到堆内存的并发转移还没来得及做重定位的对象,触发对象重定位和删转发表的记录做原子操作
五、ZGC触发时机
- 定时触发,默认为不使用,可通过ZCollectionInterval参数配置
- 预热触发,最多三次,在堆内存达到10%、20%、30%时触发,主要时统计GC时间,为其他GC机制使用
- 基于分配速率的自适应算法:最主要的GC触发方式(默认方式),其算法原理可简单描述为”ZGC根据近期的对象分配速率以及GC时间,计算出当内存占用达到什么阈值时触发下一次GC”。通过ZAllocationSpikeTolerance参数控制阈值大小,该参数默认2,数值越大,越早的触发GC。日志中关键字是“Allocation Rate”。
- 主动触发规则:类似于固定间隔规则,但时间间隔不固定,是ZGC自行算出来的时机,我们的服务因为已经加了基于固定时间间隔的触发机制,所以通过-ZProactive参数将该功能关闭,以免GC频繁,影响服务可用性。
六、如何选择垃圾收集器
- 优先调整堆的大小让服务器自己来选择
- 如果内存小于100M,使用串行收集器
- 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
- 如果允许停顿时间超过1秒,选择并行或者JVM自己选
- 如果响应时间最重要,并且不能超过1秒,使用并发收集器
- 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC