大厂面经 | 百度二面:GC 三色标记法解析

大家好,我是老周,今天给大家分享一道关于 GC 三色标记法的面试题,这是百度二面中出现的题目。接下来,我们就从概念、流程、相关机制等方面,详细梳理 GC 三色标记法的知识。

同时老周有制作相应视频,想要详细了解此篇内容的同学可以关注小破站:*********。老周也会在上面持续分享相关稿件,内容包括大厂面试系列,GO保姆教程系列、开源项目推荐系列、面试八股文等,关注老周还可解锁300+大厂面试物料(附答案讲解),golang学习物料等,感谢支持关注!

GC 三色标记法解析:

一、什么是 GC 三色标记法

GC 三色标记法是一种垃圾回收算法,核心作用是追踪和标记活跃对象,进而确定哪些对象可以被回收。简单来说,它通过给对象标记不同颜色,区分对象的活跃状态,最终只回收不活跃的对象。

(一)三色的含义

三色标记法中的三种颜色分别是白色、灰色和黑色,每种颜色对应对象的不同状态,具体如下:

(二)三色标记法的核心流程

三色标记法的执行过程主要分为以下几个步骤,我们结合对象示例来理解:

  1. 初始状态:所有对象标记为白色 一开始,堆中的所有对象(比如文档中提到的 “饭”“粥” 这类抽象对象,或具体的对象 1、2、3……)都会被标记为白色,并放入白色集合中,此时灰色集合和黑色集合为空。
  2. 标记根可达对象:根节点直接引用对象入灰色集合 首先遍历 Root 集合(根节点,比如虚拟机栈中的引用、静态变量引用等),找到从根节点直接可达的对象(假设是对象 1 和对象 6)。将这些对象的颜色从白色改为灰色,并从白色集合中移除,放入灰色集合。
  3. 遍历灰色对象:子对象入灰,自身入黑 对灰色集合中的对象(此时是对象 1 和对象 6)进行遍历:
  • 找到它们直接引用的子对象(假设是对象 2、4、7),将这些子对象从白色改为灰色,放入灰色集合;
  • 待当前灰色对象(对象 1 和对象 6)的所有子对象都处理完毕后,将它们自身的颜色从灰色改为黑色,放入黑色集合。这意味着对象 1 和对象 6 已完全标记,是确定的活跃对象。
  1. 重复遍历灰色集合:直至灰色集合为空 不断重复步骤 3:每次从灰色集合中取出一个对象,处理它的子对象(子对象变白为灰入灰色集合),处理完后自身变灰为黑入黑色集合。直到灰色集合中没有任何对象为止。
  2. 回收白色对象:清理不活跃对象 此时,白色集合中剩余的对象(比如对象 5 和对象 8),是从根节点无法到达的不活跃对象,最终会被垃圾回收器清理;而黑色集合中的对象都是活跃对象,会被保留。

二、关键机制:强弱三色不变式与写屏障

在并发 GC 场景中(GC 线程和应用线程同时运行),对象的引用关系可能会动态变化,容易出现 “漏标” 问题(即活跃对象被误判为不活跃对象,导致被回收)。为解决这个问题,引入了 “强弱三色不变式” 和 “写屏障” 机制。

(一)强弱三色不变式:避免漏标的规则

强弱三色不变式是两种约束规则,目的是防止 “黑色对象引用白色对象” 的情况导致漏标,具体定义和作用如下:

  1. 强三色不变式
  • 定义:不存在黑色对象引用白色对象。
  • 作用:从根本上杜绝漏标可能。因为黑色对象已完全标记,若它引用白色对象,白色对象无法被 GC 线程遍历到(GC 线程只处理灰色对象),最终会被误回收。强三色不变式通过禁止这种引用关系,确保所有活跃对象都能被标记。
  1. 弱三色不变式
  • 定义:若黑色对象引用白色对象,该白色对象必须有灰色的上游对象(即存在一条从灰色对象到该白色对象的引用路径)。
  • 作用:相比强三色不变式更灵活。当黑色对象引用白色对象时,只要白色对象有灰色上游,GC 线程后续遍历灰色对象时,仍能找到这个白色对象,将其标记为活跃对象,避免漏标。比如:黑色对象 7 引用白色对象 8,但灰色对象 6 也引用对象 8,那么 GC 线程遍历灰色对象 6 时,会将对象 8 标记为灰色,后续继续处理,避免漏标。

(二)写屏障:维护不变式的 “特殊逻辑”

写屏障不是 “阻止操作” 的屏障,而是一段特殊的程序逻辑:当应用线程对对象的引用指针进行写操作(新增、删除、修改引用)时,会触发这段逻辑,确保对象引用关系变化后,仍满足强弱三色不变式,从而避免漏标。

根据处理的写操作类型,写屏障主要分为 “插入写屏障” 和 “删除写屏障” 两类:

  1. 插入写屏障:处理 “新增引用” 场景
  • 触发时机:当黑色对象新增对白色对象的引用时(比如黑色对象 7 突然引用白色对象 8)。
  • 逻辑操作:将被引用的白色对象(对象 8)从白色集合中取出,标记为灰色并放入灰色集合。
  • 作用:让新增引用的白色对象能被 GC 线程后续遍历,避免漏标。比如对象 7(黑色)引用对象 8(白色)时,触发插入写屏障,对象 8 变灰入灰色集合,后续 GC 处理灰色集合时会遍历对象 8,确保其被正确标记为活跃对象。
  1. 删除写屏障:处理 “删除引用” 场景
  • 触发时机:当灰色对象删除对白色对象的引用时(比如灰色对象 7 原本引用白色对象 8,现在删除该引用)。
  • 逻辑操作:将被删除引用的白色对象(对象 8)标记为灰色,放入灰色集合。
  • 作用:防止因引用删除导致白色对象 “断连” 漏标。比如对象 7(灰色)删除对对象 8(白色)的引用后,若没有其他引用路径,对象 8 可能被误判为不活跃;触发删除写屏障后,对象 8 变灰入灰色集合,GC 线程会进一步检查它是否有其他引用,避免漏标。

(三)特殊场景:栈区对象的处理

栈区(虚拟机栈)的对象有两个特点:数量多、访问频率高。如果对栈区对象也使用写屏障,会给应用性能带来极大负担,因此栈区对象不使用写屏障。为避免栈区对象漏标,采用以下特殊处理方式:

  1. GC 标记开始时:栈区所有可达对象标黑 在 GC 标记阶段启动时,直接将栈区中所有从根节点可达的对象标记为黑色,放入黑色集合。这些对象被默认视为活跃对象,避免后续因无写屏障导致漏标。
  2. 并发标记中新增栈对象:直接标黑 在 GC 并发标记过程中,应用线程若在栈区新增对象(比如对象 9),直接将该对象标记为黑色。若不这样做,新增对象会是白色,标记结束后可能被漏标,此时需要触发 STW(Stop The World,暂停应用线程)重新扫描,影响性能。直接标黑虽可能 “误判”(将部分不活跃对象标为活跃),但可避免 STW,权衡性能后选择此方案(不活跃对象可在下一次 GC 中回收)。
  3. 栈区对象引用修改:不改变颜色 若栈区中的黑色对象发生引用删除或修改,不会改变其黑色属性。因为黑色对象已被判定为活跃对象,即使引用变化,本次 GC 也不会回收它,后续 GC 会重新判断其状态。

三、补充说明:GC 三色标记法的面试意义与实践应用

(一)面试中的 “三色标记法”

在面试中考察三色标记法,核心是检验候选人对 JVM 垃圾回收底层原理的理解深度,而非实际工作中的高频应用。具体特点如下:

  • 工作中使用率低:绝大多数开发场景中,无需直接操作或修改 GC 机制,了解三色标记法不影响日常业务开发;
  • 面试区分度高:当候选人技术水平相近时,能否回答出三色标记法的原理、强弱不变式、写屏障等细节,会成为面试的 “加分项” 甚至 “决定项”;
  • 属于 “八股文” 但必要:虽偏向理论,但能反映候选人对底层知识的钻研态度,因此成为常见面试题。

(二)实践中的应用:GC 参数调优

深入理解三色标记法等 GC 原理,虽不直接用于业务开发,但可辅助进行 GC 参数调优,比如:

  • 调整 GC 触发阈值:默认情况下,部分 GC 算法可能每 2 分钟触发一次垃圾回收,若了解 GC 标记流程,可根据应用的内存使用情况(比如堆内存增长速度),通过参数(如-XX:MaxGCPauseMillis)调整 GC 触发时机,减少 GC 对应用性能的影响;
  • 选择合适 GC 算法:不同 GC 算法(如 G1、ZGC)对三色标记法的实现细节不同(比如 G1 使用混合写屏障),了解原理可帮助选择更适配业务场景的 GC 算法。

(三)JVM GC 启动函数流程(参考源码)

老周根据 JVM 源码中 MGC(某类 GC 实现)的核心方法,整理了 GC 启动函数(类似JC Start)的大体流程,供大家参考(若有错误,欢迎在评论区留言指正):

  1. 初始化 GC 参数:读取 JVM 配置的 GC 阈值、内存区域划分等参数;
  2. 触发 GC 条件判断:检查当前堆内存使用率、是否达到触发阈值等,确认是否需要启动 GC;
  3. 暂停应用线程(部分 GC 阶段):根据 GC 算法,决定是否触发 STW(如初始标记阶段通常需要短暂 STW);
  4. 执行三色标记流程:按 “初始标白→根对象标灰→遍历灰对象标黑→清理白对象” 的步骤执行标记和回收;
  5. 恢复应用线程:标记和回收完成后,唤醒被暂停的应用线程,恢复业务执行;
  6. 记录 GC 日志:输出本次 GC 的耗时、回收内存大小等信息,便于问题排查。

同时老周有制作相应视频,想要详细了解此篇内容的同学可以关注小破站:*********。老周也会在上面持续分享相关稿件,内容包括大厂面试系列,GO保姆教程系列、开源项目推荐系列、面试八股文等,关注老周还可解锁300+大厂面试物料(附答案讲解),golang学习物料等,感谢支持关注!

#golang##程序员##计算机##it#
全部评论

相关推荐

评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务