JVM学习笔记之垃圾收集算法【四】

2020-12-20  本文已影响0人  JiaJianHuang

一、什么是垃圾回收?

垃圾回收(英语:Garbage Collection,缩写为 GC),在计算机科学中是一种自动的存储器管理机制。当一个电脑上的动态存储器不再需要时,就应该予以释放,以让出存储器,这种存储器资源管理,称为垃圾回收。垃圾回收器可以让程序员减轻许多负担,也减少程序员犯错的机会。垃圾回收最早起源于 LISP 语言。当前许多语言如 Smalltalk、Java、C#和 D 语言都支持垃圾回收器。--摘自 wiki

二、常用的垃圾回收算法

2.1 标记清除法( Mark-Sweep )

介绍

标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为两个阶段:标记阶段清除阶段。首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象

标记清除法图示

image

缺点

2.2 标记复制算法(Copying)

核心思想

算法图示

image

新生代的内存布局

image

新生代复制算法执行规则

2.3 标记整理算法(Mark-Compact)

介绍

在对象存活率较低的新生代使用复制算法效率高。那么在对象存活率高的老年代,使用复制算法效率将会变得很低。根据老年代的特点,有人提出了“标记 - 整理”算法。算法流程如下:

这种方法既避免了碎片的产生 ,又不需要两块相同的内存空间,因此,其性价比较高。

算法工作图示

image

三、HotSpot 算法实现

3.1 根节点枚举

存在问题:

解决方案:

在 HotSpot 的解决方案里,是使用一组称为 OopMap 的数据结构来解决

3.2 安全点(Safe Point)

问题:

解决:

对于安全点,另外一个需要考虑的问题是,如何在垃圾收集发生时让所有线程(这里其实不包括执行 JNI 调用的线程)都跑到最近的安全点,然后停顿下来。有以下两种方式:

3.3 安全区域(Safe Region)

安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点。

问题:

但是当线程没有分配 CPU 时间(如线程处于 Sleep 状态或者 Blocked 状态),这时候线程无法响应 JVM 的中断请求以继续到安全的地方去中断挂起,JVM 也显然不太可能等待线程重新被分配 CPU 时间。对于这种情况,就需要安全区域(Safe Region)来解决。

安全区域(Safe Region):

是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。

3.4 记忆集与卡表(Remembered Set)

问题:

为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集(Remembered Set)的数据结构,用以避免把整个老年代加进 GC Roots 扫描范围。来缩减 GC Roots 扫描范围的问题。

记忆集:

是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。

记录精度

卡表”(Card Table)

记录精度中,第三种“卡精度”所指的是用一种称为“卡表”(Card Table)的方式去实现记忆集。卡表最简单的形式可以只是一个字节数组。以下这行代码是 HotSpot 默认的卡表标记逻辑

CARD_TABLE[this.address >> 9] = 0;

字节数组 CARD_TABLE 的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以 2 的 N 次幂的字节数。

卡表与卡页对应示意图:

image

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为 1,称为这个元素变脏(Dirty),没有则标识为 0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入 GC Roots 中一并扫描。

3.5 写屏障(Write Barrier)

解决了如何使用记忆集来缩减 GC Roots 扫描范围的问题,但还没有解决卡表元素如何维护的问题,例如它们何时变脏、谁来把它们变脏等。

卡表元素何时变脏?:

卡表元素如何变脏: 即如何在对象赋值的那一刻去更新维护卡表呢?

写屏障(Write Barrier)

在 HotSpot 虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的。注意这里的写屏障与解决并发乱序执行问题中的“内存屏障”区分开来。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的 AOP 切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。

问题:

解决“伪共享”(False Sharing)问题:

3.6 并发的可达性分析

三色标记(Tri-color Marking):

关于可达性分析的扫描过程:

初始状态: 只有 GC Roots 是黑色,注意图中的箭头,引用是有向的,对象只有被黑色对象引用才能活,否则,如果没有黑色对象引用它,它再怎么引用其他对象都是会消亡的。看以下图示:

image

扫描过程中,以灰色为波峰的波纹从黑向白推进,灰色对象是黑、白对象的分界线

image

扫描顺利完成,此时黑色对象是可存活对象,白色对象是已消亡可回收对象。

image

但用户线程在标记进行时并发修改了引用关系,扫描就不会如此顺利完成了。
如:在波纹推进过程中,正在扫描的灰色对象的一个引用被切断了,同时原来的引用对象又与扫描过的黑色对象建立了引用关系

image

又譬如,这种切断后重新被黑色对象引用的对象可能是原有引用链中的一部分。
由于黑色对象不会重新扫描,这将导致扫描结束后出现两个被黑色对象引用的对象仍是白色,这个对象就会消失,这就很危险了

image

并发扫描时的对象消失问题:

解决:只需破坏这两个条件的任意一个即可

以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。在 HotSpot 虚拟机中,增量更新和原始快照这两种解决方案都有实际应用,譬如,CMS 是基于增量更新来做并发标记的,G1、Shenandoah 则是用原始快照来实现。

参考

上一篇 下一篇

猜你喜欢

热点阅读