现代垃圾收集器
大家好,我是你们的花姐。
话说java的长期支持版本已经发展到了JDK21,大部分同学对jvm中的垃圾收集器还停留在java8之前的CMS和G1。对java11之后引入的低延迟垃圾收集器shenandoah和zgc几乎是一无所知,甚至有同学是连这两个名字也没有听过呀,嘻嘻。
今天我们就一起来解开这两个低延迟垃圾收集器的神秘面纱。
G1
在了解现代垃圾收集器之前,我们先来回顾下我们的老朋友G1的垃圾收集过程。后面大家会发现,本文的重点低延迟垃圾收集器可是和G1有着诸多关联呢。
首先G1开创了分区收集的先河,它将整堆划分为多个大小相等的region,又为大对象分配了容量为N个region大小的大对象H区,收集器对每个region独立进行垃圾回收。
G1的垃圾收集过程
G1的垃圾收集整体经过 初始标记、并发标记、最终标记、筛选回收四个阶段。其中的最终标记和筛选回收阶段是不支持并发的,这些垃圾收集阶段用户线程不能同时运行。
Shenandoah
Shenandoah作为G1的继承者,与G1采用同样的基于Region的堆内存布局,也有用于大对象存放的H区,默认回收策略也是基于Region的回收价值,会优先处理回收价值最大的区域。
Shenandoah与G1的不同体现在以下三个方面:
-
- Shenandoah支持并发的整理算法,G1虽也是多线程回收但收集器回收现在不能与用户线程并发执行。
- Shenandoah默认不使用分代算法,不对Region再进行分代。
- Shenandoah摒弃了在G1中需要消耗大量内存和计算资源维护的记忆集,而使用了“连接矩阵”(connect matrix)这种全局数据结构来记录跨区引用关系。
Shenandoah垃圾收集过程
- 初始标记:需要STW,首先标记与GC Roots直接关联的对象,同G1相同;
- 并发标记:与用户线程并发,遍历对象图,标记所有可达的对象,同G1相同;
- 最终标记:需要STW,时间很短,处理剩余SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set),同G1相同;
- 并发清理:这个阶段用于清理哪些整个区域内连一个存活对象都没有找到的Region(这类Region称为:Immediate Garbage Region)
- 并发回收:Shenandoah要把回收集中存活对象先复制一份到其他未被使用Region中。由于该阶段是和用户线程并发执行,Shenandoah通过读屏障和"Books Pointers"的转发指针来解决。该阶段时间长短取决于回收集的大小。
- 初始引用更新:需要STW,时间很短,并发回收阶段复制对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。本阶段未做具体处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。
- 并发引用更新:与用户线程一起并发执行,真正开始引用更新操作,不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性的搜索出引用类型,把就只改为新值即可。
- 最终引用更新:解决了堆中的引用更新后,还要修正GC Roots中的引用。最后一次STW,停顿时间只和GC Roots数量有关,
- 并发清理:经过上述步骤,整个回收集中所有的Region已无存活对象,这些Region都变成Immediate Garbage Region,最后回收这些空间,提供给以后的新对象使用。
整个垃圾回收过程中涉及到的回收阶段较多,可以重点关注并发标记、并发回收和并发引用更新操作的实现。
转发指针
垃圾收集阶段进行垃圾回收使用标记整理算法,将多个区域的存活对象拷贝到空闲的region中。由于回收阶段收集器线程和用户线程并发运行,此时对象移动后会涉及到对象的引用更新的问题。
shenandoah通过在每个对象的对象头上创建一个Brooks Pointer转发指针,来存储新对象的副本,以进行访问转发。
shenandoah使用转发指针和读屏障来实现并发整理回收;
ZGC
ZGC和shenandoah一样都是低延迟垃圾收集器,其目标可阐述为在不影响吞吐量的情况下,实现任意堆内存大小都可以把垃圾收集的停顿时间限制在10ms内。
主要特征:ZGC是一款基于region内存布局,使用读屏障、染色指针、内存多重映射等技术来实现可并发的标记-整理算法。是以低延迟为首要目标的一款垃圾收集器。
区别于G1和shenandoah,ZGC的region具有动态性:
-
- 可以动态的创建和销毁region
- 每个region的容量大小可变,也具有动态性。
ZGC的region按容量可分为3种类型:
小型region(small):容量固定为2MB,用于存放小于256KB的对象。
中型region(middle):容量固定为32MB,用于存放占用内存在256KB-4MB的对象。
大型region(lager):容量不固定,可以动态变化,但必须是2MB的整数倍。存放占用内存大于4MB的大对象,一个大型region只会存放一个大对象,且该对象不会被重新分配。
ZGC的垃圾回收过程
ZGC的垃圾回收过程大致可以分为4个阶段,4个阶段中的每个阶段都是并发执行的,只是在各阶段的中间会有短暂的stw停顿。
并发标记
遍历对象图进行可达性分析,使用染色指针算法进行可达性分析,对象的可达性标记在染色指针上。
并发预备重分配
扫描所有的region,如果该region有存活对象需要重新分配(复制到其他region),则将该region加入到重新分配集。
并发重分配
在这个阶段,ZGC会将重新分配集中region的存活对象复制到一个新的region中,并为重新分配集中的每一个region维护一个转发表,用于记录旧对象到新对象的映射关系。
如果有用户线程访问了处于并发重分配中的对象,可通过对象的指针发现对象region正处于重新分配集中,该次访问就会被读屏障截获,读屏障通过转发表中的内容来转发该次访问,并修改引用中的值,使其直接指向新对象。这便是ZGC的自愈过程。
另外,如果一个region中的所有对象都已经复制完毕,该region就可以进行回收了。保留的转发表会进行对象访问的转发,ZGC会在下个阶段来完成对象的引用更新。
并发重映射
最后一步的工作就是要修正所有的指针并释放转发表。由于region中的对象复制完毕后,可以提前进行内存回收,因此并发重映射的优先级并不高。ZGC将该阶段的处理合并到下一次的并发标记过程中,反正下一次的并发标记阶段也是要遍历所有对象。
以上所述的垃圾收集过程中涉及到以下独有的专业名词:染色指针、读屏障、转发表...,可能会让人有些摸不着头脑,下面分别对它们予以说明。
染色指针
6 4 4 4 4 4 03 7 6 5 2 1 0
+-------------------+-+----+-----------------------------------------------+
|00000000 00000000 0|0|0100|11 11111111 11111111 11111111 11111111 11111111|
+-------------------+-+----+-----------------------------------------------+
| | | |
| | | * 41-0 Object Offset (42-bits, 4TB address space)
| | |
| | * 45-42 Metadata Bits (4-bits) 0001 = Marked0
| | 0010 = Marked1
| | 0100 = Remapped
| | 1000 = Finalizable
| |
| * 46-46 Unused (1-bit, always zero)
|
* 63-47 Fixed (17-bits, always zero)
染色指针是一种将少量额外信息存储在指针上的技术。
在常用的64位机器中,选取了其中的4位来作为染色指针。
在64位的系统中,基于成本、需求、性能的考虑在64位的Linux系统中,支持46位的物理地址内存空间。其中64位中的前18位未被使用,ZGC从剩余的46位中取出4个高位来存储4个标志信息。
因此ZGC最终可管理的内存空间不超过4TB(2的42次幂)。
通过4个标志位,虚拟机可以看到该对象的三色标记状态。
染色指针的变化过程
M0、M1、Remapped三者是互斥的,只能有一位为1。如100,称为M0视图;010称为M1视图;001称为Remapped视图。
Remapped表示对象指针已经重新定位,可以直接使用。M0 或M1 表示对象指向老地址,需要重新定位。
开始GCROOTS扫描前,所有对象的指针都是指向Remapped。
并发标记阶段,开始对象扫描后,会将扫描到的存活对象标记为M0,扫描过程中新创建的对象也会直接标记为M0
并发重分配阶段会进行存活对象复制,复制完成后对象指针由M0指向Remapped。
在并发重映射阶段即下一次垃圾收集的并发标记阶段,会将扫描到的Remapped指针指向M1,用来和上一次扫描的存活对象做区分。
下一次并发标记阶段开始后,会将扫描到的存活对象指针标记为M1。
并完成上一次垃圾收集过程中Remapped指针对象的引用更新和相关转发表的回收。
由此可以看出,M0和M1两个指针在垃圾收集过程中是循环使用的。
读屏障
ZGC通过使用读屏障来完成指针的自愈,以支撑并发重分配过程中的用户线程的对象访问。
转发表
转发表:对象复制完成,会将原对象和新对象之间的映射关系保存到转发表中。
以上就是今天的全部内容了。研究一个新技术点,特别是在参考资料很少的情况下,学习难度还有有一些的,可能会导致开始时我们的学习效率并不高,很多时候想要放弃。但是保持积极的态度和耐心,不轻易放弃,让自己长期处于学习的拉伸区,蓦然回首就会发现自己已经攀登上了一座又一座高山!