ZGC 收集器
ZGC全称Z Garbage Collector。是由Oracle公司研发的,在2018年提交给OpenJDK的,基于JDK11的一款垃圾收集器。
ZGC的目标
ZGC的目标是在尽可能对吞吐量(吞吐量与停顿时间成反比)影响不大的前提下,实现在任意堆大小下都可以把垃圾收集的停顿(Stop The World)时间限制在十毫秒以内的低延迟。
ZGC的Region
ZGC采用基于Region的内存布局(也有一些资料称为Page或者ZPage),ZGC的Region具有动态性,动态创建和销毁,以及动态的区域容量大小。在x86架构下,ZGC的Region分为大、中、小三类容量:
- 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
- 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
- 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍。用于放置4B或以上的大对象。每个大型Region中只会存放一个大对象。
染色指针
染色指针(Colored Pointer)是ZGC的核心技术,了解ZGC的运作过程前,先来了解下染色指针。
ZGC的染色指针,是一种直接将少量额外信息存储在指针上的技术。
ZGC仅支持64位系统。在64位系统中,高18位不能用来寻址,剩余的46位可以被用来寻址,ZGC将这46位中的高4位(ZGC将对象存活信息存储在42~45位中)提取出来存储四个标志位,通过这些标志位,虚拟机可以直接从指针中看到其引用对象的三色标记状态、是否进入了重分配集(即被移动过)、是否只能通过finalize()方法才能访问到。这种方式进一步压缩了只有46位的地址空间,导致ZGC能够管理的内存不可以超过4TB。
在对象创建时,ZGC同时会为该对象在M0、M1和Remapped地址空间分别申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址,但这三个空间在同一时间有且只有一个空间有效。在对象被ZGC标记过程中,这三个空间将会发生切换,这里就不详细描述切换过程了。
这时,可达性分析遍历对象图来标记对象,可以说是遍历“引用图”来标记“引用”了。
读屏障
读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。
Object o = obj.FieldA // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i = obj.FieldB //无需加入屏障,因为不是对象引用
在ZGC中,应用线程访问对象将触发“读屏障”,如果发现对象被移动了,那么“读屏障”会把读出来的指针更新到对象的新地址上,这样应用线程始终访问的都是对象的新地址。
ZGC的运作过程
四大阶段
ZGC采用标记-复制算法。ZGC的运作过程分为4大阶段,其中ZGC把第一阶段Mark和最后一阶段Remap合并了(稍后讲解合并原因)。
- 并发标记(Concurrent Mark):遍历对象图做可达性分析,但ZGC的标记是在指针上进行的,标记阶段会更新染色指针中的Marked0、Marked1标志位。标记过程是针对全堆的。
- 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region。
- 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。
- 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用。这个操作要遍历对象图,所以ZGC将此过程合并到了下一次垃圾收集循环的并发标记阶段,省去了一次遍历对象图的开销。
指针的自愈能力:
在并发重分配阶段,维护转换表的过程中,如果用户线程在此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将此访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈(Self-Healing)”能力。这样做的好处是只有第一次访问旧对象会陷入转发,也就是只慢一次。
这四个大阶段总的停顿时间小于10ms。
详细过程:
ZGC的详细GC过程只有三个STW(Stop The World)阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加
ZGC触发时机
主要是 基于分配速率的自适应算法,其他方式暂且不提。
算法原理可简单描述为”ZGC根据近期的对象分配速率以及GC时间,计算出当内存占用达到什么阈值时触发下一次GC”。日志中关键字是“Allocation Rate”
一次完整的GC日志
ZGC的优缺点
优点
- 停顿时间不超过10ms;
- 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
- 支持8MB~4TB级别的堆(未来支持16TB)
ZGC适用于大内存低延迟服务的内存管理和回收
缺点
能承受的对象分配速率不能太高。
对象分配速率高,将创造大量的新对象,这些新对象很难进入当次收集的标记范围,只能当做存活对象,这就产生了大量的浮动垃圾。每次回收到的内存小于浮动垃圾所占的空间大小,堆中的可用空间就越来越小了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。