面试官:并发标记时如何标记垃圾 & 垃圾回收之三色标记法详解

一、为什么需要三色标记算法

JVM 中的垃圾回收是基于 标记-复制、标记-清除 和 标记-整理 三种模式的,那么其中最重要的其实是如何标记

  • 像Serial、Parallel这类的回收器,无论是单线程标记和多线程标记,其本质采用的是暂停用户线程进行全面标记的算法,这种算法的好处就是标记的很干净,而且实现简单,缺点就是标记时间相对很长,导致STW的时间很长。
  • 那么后来就有了并发标记,适用于CMS和G1,并发标记的意思就是 可以在不暂停用户线程的情况下对其进行标记,那么实现这种并发标记的算法就是 三色标记法,三色标记法最大的特点就是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个GC。

二、三色标记算法基本原理

原理分析

要找出存活对象,根据可达性分析,从GC Roots开始进行遍历访问,可达的则为存活对象:
在这里插入图片描述
最终结果:A、D、E、F、G 可达

我们把遍历对象图过程中遇到的对象,按 “是否访问过” 这个条件标记成以下三种颜色:

  • 白色:尚未被GC访问过的对象,如果全部标记已完成依旧为白色的,称为不可达对象,既垃圾对象
  • 黑色:本对象已经被GC访问过,且本对象的子引用对象也已经被访问过了
  • 灰色:本对象已访问过,但是本对象的子引用对象还没有被访问过,全部访问完会变成黑色,属于中间态

标记过程分析

在这里插入图片描述
图片链接:https://www.jianshu.com/p/12544c0ad5c1

  • 步骤一:在GC并发标记刚开始时,所以对象均为白色集合;
  • 步骤二:将所有GCRoots直接引用的对象标记为灰色集合
  • 步骤三:判断灰色集合中的对象:
    • 若对象 不存在 子引用,则将其放入 黑色集合
    • 若对象 存在 子引用对象,则 将其所有的子引用对象放入灰色集合当前对象放入黑色集合
  • 步骤四:按照步骤三,以此类推,直至灰色集合中的所有对象变成黑色后,本轮标记完成且当前白色集合内的对象称为不可达对象,既垃圾对象

三色标记算法的缺点

问题:由于此过程是在和用户线程并发运行的情况下,对象的引用处于随时可变的情况下,那么就会造成多标和漏标的问题

浮动垃圾:本应该被标记为白色的对象,没有被标记,造成该对象可能不会被回收

比如:

  • E对象在GC扫描D对象时,E还正在被D引用,那么此时E就被标记为灰色;
  • 此时业务逻辑的变化,D指向E的引用被置空了,这时候E以及后续子引用本应该被当成垃圾回收,但是此时E已经被标记为灰色,导致E对象以及其子对象没有被及时清理掉,变成了浮动垃圾,还有在并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。

漏标:灰色对象指向白色对象的引用消失了,然后一个黑色的对象重新引用了白色对象。这种错误必浮动垃圾更为严重。

比如:

  • D对象引用E对象,E引用G,此时GC正好处于D已经变成黑色,E处于灰色;
  • G是白色的情况下,此时因为业务逻辑的变化,E不引用G了,D对象引用了G
  • 按照三色标记法看,黑色对象是已完成状态,不可能再去找子引用,所以G就不会变成灰色,这样就会造成白色对象此时正在被线程使用中,但是无法被标记成灰色或者白色,造成一个正在被使用的对象被错误回收

不难分析,漏标只有同时满足以下两个条件时才会发生:

  • 条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。
  • 条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。

三色标记算法的解决方案

CMS:Incremental Update算法

当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描。(破坏条件二)

G1:SATB(Snapshot At The Beginning)算法

当原来成员变量的引用发生变化之前,记录下原来的引用对象,既原始快照,当B和C之间的引用马上被断掉时,将这个引用记录下来,使GC依旧能够访问到,那样白色就不会漏标。(破坏条件一)

STAB算法 和 Incremental Update算法 对比:

  • SATB 算法是关注引用的删除。(B => C 的引用)
  • Incremental Update 算法关注引用的增加。(A => C 的引用)
  • G1 如果使用Incremental Update 算法,因为变成灰色的成员还要重新扫,重新再来一遍,效率太低了。所以G1 在处理并发标记的过程比CMS 效率要高,这个主要是解决漏标的算法决定的。

三、跨代引用

堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要跟踪从老年代到新生代的所有引用,所以要避免每次YGC 时扫描整个老年代,减少开销

Rset(记忆集)

RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可

CardTable(卡表)

在这里插入图片描述
卡表的意思就是将一个Region区分为若干个card,组成的一张card表,可以将其理解为数组card[i],Rset的存储结构就相当于hashMap,key值为引用当前Region区的另外一个Region区的内存地址,value就是另外一个Region区中引用本Region区的card的索引值,意思就是GC可以通过Rset快速找到是哪个Region的哪个card对本Region进行了引用。

四、GC参数整理

GC 常用参数

-Xmn -Xms -Xmx –Xss 年轻代最小堆最大堆栈空间
-XX:+UseTLAB 使用TLAB,默认打开
-XX:+PrintTLAB 打印TLAB 的使用情况
-XX:TLABSize 设置TLAB 大小

-XX:+DisableExplicitGC 启用用于禁用对的调用处理的选项System.gc()
-XX:+PrintGC 查看GC 基本信息
-XX:+PrintGCDetails 查看GC 详细信息
-XX:+PrintHeapAtGC 每次一次GC 后,都打印堆信息
-XX:+PrintGCTimeStamps 启用在每个GC 上打印时间戳的功能
-XX:+PrintGCApplicationConcurrentTime 打印应用程序时间(低)
-XX:+PrintGCApplicationStoppedTime 打印暂停时长(低)
-XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用(重要性低)
-verbose:class 类加载详细过程
-XX:+PrintVMOptions 可在程序运行时,打印虚拟机接受到的命令行显示参数
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 打印所有的JVM 参数、查看所有JVM 参数启动的初始值(必须会用)
-XX:MaxTenuringThreshold 升代年龄,最大值15, 并行(吞吐量)收集器的默认值为15,而CMS 收集器的默认值为6。

Parallel 常用参数

-XX:SurvivorRatio 设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为8
-XX:PreTenureSizeThreshold 大对象到底多大,大于这个值的参数直接在老年代分配
-XX:MaxTenuringThreshold 升代年龄,最大值15, 并行(吞吐量)收集器的默认值为15,而CMS 收集器的默认值为6。
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU 核数相同
-XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS 常用参数

-XX:+UseConcMarkSweepGC 启用CMS 垃圾回收器
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU 核数相同

-XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS 收集,默认是68%(近似值),如果频繁发生SerialOld 卡顿,应该调小,(频繁CMS 回收)
-XX:+UseCMSCompactAtFullCollection 在FGC 时进行压缩
-XX:CMSFullGCsBeforeCompaction 多少次FGC 之后进行压缩
-XX:+CMSClassUnloadingEnabled 使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。
-XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm 回收,JDK 8 中不推荐使用此选项,不能替代。
-XX:GCTimeRatio 设置GC 时间占用程序运行时间的百分比(不推荐使用)
-XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代

G1 常用参数

-XX:+UseG1GC 启用CMS 垃圾收集器
-XX:MaxGCPauseMillis 设置最大GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且JVM 将尽最大的努力(G1 会尝试调整Young 区的块数来)来实现它。默认情况下,没有最大暂停时间值。
-XX:GCPauseIntervalMillis GC 的间隔时间
-XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size 增加,垃圾的存活时间更长,GC 间隔更长,但每次GC 的时间也会更长
-XX:G1NewSizePercent 新生代最小比例,默认为5%
-XX:G1MaxNewSizePercent 新生代最大比例,默认为60%
-XX:GCTimeRatioGC 时间建议比例,G1 会根据这个值调整堆空间
-XX:ConcGCThreads 线程数量
-XX:InitiatingHeapOccupancyPercent 启动G1 的堆空间占用比例,根据整个堆的占用而触发并发GC 周期

参考:https://blog.csdn.net/u010800201/article/details/107684597

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熠熠98

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值