当前位置: 首页 > 科技观察

ZGCGarbageCollector

时间:2023-03-12 10:09:20 科技观察

ZGC概述ZGarbageCollector,也称为ZGC,是一个可扩展的低延迟垃圾收集器,在jdk11中引入,并在jdk15中作为稳定版本发布。旨在满足以下目标:<1ms最大暂停时间(jdk<16为10ms,jdk>=16为<1ms)。暂停时间不会随着堆、活动集或根集的大小而增加。适用于内存大小从8MB到16TB的堆。ZGC具有以下特点:并发性基于区域压缩NUMA感知使用彩色指针使用负载屏障ZGC的核心是并发垃圾收集器,这意味着所有繁重的工作都在Java线程继续执行的同时完成。这极大地限制了垃圾收集对应用程序响应时间的影响。ZGC特性ZGC收集器基于Region内存布局,(暂)无生成,利用读屏障、彩色指针、内存多重映射等技术实现并发标记-排序算法,具有低延迟的垃圾收集器首要目标。内存布局ZGC没有生成ZGC的内存布局的概念。与Shenandoah和G1一样,ZGC也采用了基于Region的堆内存布局,但与它们不同的是,ZGC的Region是动态的(动态创建和销毁,动态region容量大小)。在x64硬件平台下,ZGC的Region可以有大、中、小三种容量(如下图):SmallRegion(小区域):容量固定为2M,存储小于256K的对象。MediumRegion(中型区域):容量固定为32M,放置大于等于256K但小于4M的对象。LargeRegion(大区域):容量不固定,可以动态变化,但必须是2MB的整数倍,用于放置4MB以上的大对象。NUMA-awareNUMA对应NMA,UMA是UniformMemoryAccessArchitecture,NUMA是NonUniformMemoryAccessArchitecture。UMA的意思是只有一个内存,所有的CUU都要访问这些内存,所以会存在竞争问题(内存总线访问权的竞争),如果存在竞争,就必须加锁,锁的效率会受到影响,CPU核心数越多,竞争越激烈。在NUMA中,每个CPU对应一个内存块,而这块内存在主板上离CPU最近,每个CPU都有优先访问这块内存的权利,效率自然会提高。服务器的NUMA架构在中大型系统即高性能解决方案中非常流行,尤其是在系统延迟方面。ZGC是一种可以自动感知NUMA架构并充分利用的特性。彩色指针(ColoredPointer)彩色指针,即彩色指针,如图所示,是ZGC的核心设计之一。以前的垃圾收集器的GC信息保存在对象头中,而ZGC的GC信息保存在指针中(直接在对象的引用指针上记录标记信息)。每个对象都有一个64位的指针,这64位分为:18位:留作以后使用。1位:Finalizable标志位,该位与并发引用处理有关,表示只能通过finalizer访问这个对象(finalizer:对象基类的一个空方法,如果被重写,会在GC之前调用,这个方法只会被调用一次)。1位:重映射标志。设置该位的值后,对象不指向relocationset(relocationset表示需要GC的Region集合)。1位:Marked1标识。1位:Marked0标识,上面的Marked1都是marked对象,用于辅助GC。42位:对象的地址(所以可以支持2^42=4T内存):为什么有两个标记标记?在每个GC周期开始时,使用的标记位将被交换,从而使在上一个GC周期中纠正的标记状态失效,所有引用都变为未标记状态。GC循环1:使用mark0,循环结束时所有引用标记都会变成01。GCcycle2:使用mark1,和cycle1一样,所有mark标记都会变成10。ZGC不能做指针压缩?指针压缩是指压缩到32位,寻址位数不能超过35位,即JVM内存最大为32G(2^35=32GB),这里的寻址位数达到了42位。彩色指针的三大优势?当一个Region中所有存活的对象都被remove掉(复制走后),这个Region就可以马上释放,因为它还有一个转发表,记录了原地址和新地址,所以理论上只要还有一个Regionobjectfree,ZGC可以完成垃圾回收。颜色指针具有指针的“自愈”能力,减少了写屏障(如三色标记中的增量更新或原始快照),只需一个读屏障即可解决问题,减少了数量使用的内存屏障。颜色指针具有很大的可扩展性,因为有18个未使用的位,更有利于后续功能的扩展。多映射寻址中不同虚拟机内存和物理内存之间的转换关系可以在硬件层面、操作系统层面或软件层面实现。在Linux平台上,ZGC使用多重映射(Mult-Mapping)将多个不同的虚拟内存地址映射到同一个物理内存地址。这是一个多对一的映射。一个著名的ZGC看到机器的地址空间大于机器的堆内存容量。将染色指针中的标志位视为地址分段器,只要将这些不同的地址分段器映射到同一个福利空间,经过多映射转换后,就可以直接使用染色指针??进行寻址,如下图所示:multi-mapping技术确实可能会带来一些额外的好处,比如更容易复制大对象,但是从源头上看,ZGC的multi-mapping只是彩色指针的衍生,并不是为了特殊的实现一些其他的特性需求而做的。读屏障ZGC采用读屏障的方式来纠正指针引用。由于ZGC采用的是复制排序的方式进行GC,所以很可能是程序在对象位置发生变化而指针位置还没有更新后调用对象。那么此时程序需要并行获取对象的引用时,ZGC会读取对象的指针,判断Remapped标记。如果标记为该对象位于本次需要清理的region区域,则该对象的内存地址发生变化,将指针中的新引用地址替换原对象的引用地址,并且然后返回。这样,读屏障的使用就解决了并发GC的对象读问题。对象o=obj.fieldA;//从堆中加载对象引用<此处需要加载屏障>Objectp=o;//没有屏障,不是来自heapo.doSomething()的负载;//没有屏障,不是来自堆的负载i=obj.fieldB;//没有barrier,不存在对象referenceLoadBarriers,所以配置了ZGC的应用吞吐量会降低。官方测试数据需要额外4%的开销:ZGC工作过程ZGC的运行过程分为以下四个阶段:ZGCprocessingprocess.png并发标记(ConcurrentMark):同G1和Shenandoah,并发标记是遍历对象图进行可达性分析阶段。它也经历了类似于G1的短暂停顿,Shenandoah的initialmark,finalmark(虽然ZGC中的名字不叫这些),而这些停顿阶段所做的与目标也类似。与G1和Shenandoah不同的是,ZGC的标记是在指针而不是对象上进行的,标记阶段会更新染色指针中的Marked0和Marked1标志位。ConcurrentPrepareforRelocate:这个阶段需要根据具体的查询条件,计算本次收集过程中要清理哪些Region,并将这些Region组成一个RelocationSet。重分配集和G1收集器的收集集还是有区别的。ZGC划分Region的目的并不是像G1一样进行收益优先的增量回收。相反,ZGC在每次回收时都会扫描所有Region,节省了G1中设置的内存的维护成本,换取更大范围的扫描成本。所以,ZGC的reallocationset只是决定了它里面存活的对象会被重新复制到其他Region,释放里面的Region,但是不能说回收行为只针对这个set里面的Region,因为标记过程是针对整个堆的。另外,JDK12的ZGC中支持的类卸载和弱引用处理也在这个阶段完成。ConcurrentRelocate:再分配是ZGC执行的核心阶段。这个过程将重分布集中存活的对象复制到新的Region中,并为重分布集中的每个Region维护一张转发表(ForwardTable),记录了旧对象到新对象的转向关系。得益于彩色指针的支持,ZGC收集器仅从引用就可以清楚地知道一个对象是否在reallocationset中。如果此时用户线程并发访问reallocationset中的对象,则此次访问会被预设的内存屏障拦截,然后立即根据Region上的转发表记录将访问转发到新复制的对象,以及同时更正和更新引用的值,使其直接指向新对象。ZGC称这种行为为指针的“自愈”能力。这样做的好处是只有第一次访问旧对象才会陷入转发,也就是只慢一次。与Shenandoah的Brooks转发指针相比,是每次对象访问都必须付出的固定开销。简而言之,每次都很慢,因此ZGC对用户程序的运行时负载低于Shenandoah。另一个直接的好处是,由于有色指针的存在,一旦reallocationset中某个Region中的所有存活对象都被复制完毕,该Region可以立即释放,用于分配新的对象(但转发表必须保持Released状态),即使堆中有很多未更新的指向这个对象的指针,也没关系。一旦这些旧指针被使用,它们就可以自我修复。ConcurrentRemap:remapping所做的是纠正整个堆中reallocationset中旧对象的所有引用。这和Shenandoah的并发引用更新阶段从target的角度来看是一样的,但是ZGC的并发重映射并不是必须“紧急”完成的任务,因为前面说过,即使是老引用,它也可以治愈本身。最多只是在第一次使用时多做一次转发和纠错操作。重映射清理这些旧引用的主要目的是不减慢速度(以及清理后释放转发表的附带好处),所以这不是很“紧急”。因此,ZGC巧妙地将并发重映射阶段要做的工作合并到下一个垃圾回收周期的并发标记阶段。反正都需要遍历所有对象,合并就省了一个遍历对象的开销。一旦所有的指针都固定下来,就可以释放记录新旧对象关系的原始转发表。ZGC核心参数ZGC触发时机ZGC中的几种触发GC场景:定时触发:默认不使用,可以通过ZCollectionInterval参数进行配置。GC日志中的关键字“Timer”。预热触发:最多三次,在堆内存空间达到10%、20%、30%时触发,主要是通过GC时间,为其他GC触发做准备。GC日志关键字“预热”。Allocationrate:根据正态分布统计,计算99%内存的最大可能分配率,以及内存将在该速率下耗尽的时间点,并在耗尽前触发GC(耗尽时间,一次GC的最大持续时间-一个GC检测周期时间)。GC日志关键字“AllocationRate”。Proactivetrigger:(默认开启,可通过ZProactive参数配置)当堆内存比上次GC增加10%,超过5分钟,与上次GC的间隔时间限制相比(一次最长持续时间)GC),超过则触发。GC日志关键字“Proactive”。元数据分配触发:元数据区不足导致,GClogkey为"MetadataGCThreshold"直接触发:代码显示调用System.gc()被触发,GClogkey为"System.gc()”。阻塞内存分配请求触发:垃圾对象来不及挥动,占据了整个堆空间,导致部分线程阻塞,GC日志中的关键词是“AllocationStall”。ZGC日志分析下面我们将通过一个简单的程序来做一个ZGCLOG分析,下面是具体的代码和分析。下面的示例代码是一个简单的代码:/***VMArgs:-XX:+UseZGC-Xmx8m-Xlog:gc**/publicclassHeapOOM{publicstaticvoidmain(String[]args){Listlist=newArrayList<>();while(true){list.add(新字节[2048]);}}}GC日志分析GC日志如下(运行环境JDK17),例如:每一行标记GC过程的信息,关键信息如下:Start:启动GC,并注明原因用于GC触发器。上图中触发的原因是自适应算法。Phase-PauseMarkStart:初始标记,将STW。Phase-PauseMarkEnd:再次标记,willSTW。Phase-PauseRelocateStart:初始转移,将STW。Heapinformation:记录GC过程中Mark和Relocate前后的heap大小变化。High和Low记录最大值和最小值。我们一般关注High中Used的值。如果达到100%,肯定是GC过程中内存分配不足。需要调整GC触发的时机,早点或者晚点FastGC。GC信息统计:可以定时打印垃圾收集信息,观察开机到现在10秒、10分钟、10小时内的所有统计信息。使用这些统计信息,您可以排查和定位一些异常点。ZGC总结本文主要从概念上描述ZGC的特点和工作过程。目前大部分互联网公司使用的还是jdk8和jdk11,主流使用是ParNew+CMS组合或者G1。对于我们一线Java开发人员来说,更应该有学习新技术的热情和关注度,才能在激烈的社会竞争中保持优势。参考深入理解JVM虚拟机第三版周志明。https://wiki.openjdk.java.net/display/zgc/Main。http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf。https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html。