如何应用三色标记算法解决并发标记变量漏标问题

来源:Nodejs社区作者:弥生美月头衔:网络博主
导读:本期聚焦于小伙伴创作的《如何应用三色标记算法解决并发标记变量漏标问题》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何应用三色标记算法解决并发标记变量漏标问题》有用,将其分享出去将是对创作者最好的鼓励。

三色标记算法通过将对象分为白色、灰色、黑色三种状态,来实现垃圾回收的标记过程,在并发标记场景中,由于用户线程和标记线程同时运行,很容易出现变量漏标的情况,需要特定的机制来规避这个问题。

如何应用三色标记算法解决并发标记变量漏标问题

三色标记算法基础概念

三色标记算法对对象的标记状态定义如下:

  • 白色:对象尚未被标记线程访问过,初始状态下所有对象都是白色
  • 灰色:对象已经被标记线程访问过,但是该对象引用的其他对象还没有被全部扫描
  • 黑色:对象已经被标记线程访问过,且该对象引用的所有对象都已经被扫描完成

标记过程从GC Roots出发,先将GC Roots直接引用的对象标记为灰色,然后不断从灰色对象集合中取出对象,将其引用的白色对象标记为灰色,自身标记为黑色,直到灰色集合为空,剩余的白色对象就是需要回收的垃圾对象。

并发标记漏标问题的产生原因

在并发标记阶段,标记线程和用户线程同时运行,会出现两种破坏三色标记规则的情况,进而导致漏标:

情况一:黑色对象插入了指向白色对象的引用

原本黑色对象不会再次被扫描,如果它新引用了白色对象,而这个白色对象没有被其他灰色对象引用,就会导致该白色对象被漏标,最终被错误回收。

情况二:灰色对象删除了指向白色对象的引用

灰色对象原本引用了某个白色对象,在标记完成前删除了这个引用,同时没有其他灰色或黑色对象引用该白色对象,这个白色对象也会被漏标。

漏标问题的两种解决方案

增量更新(Incremental Update)

增量更新的思路是当黑色对象新插入了指向白色对象的引用时,就将这个新插入的引用记录下来,等并发标记完成之后,再将这些记录过的引用关系中的黑色对象重新标记为灰色,重新扫描一遍。

以下是增量更新记录引用关系的简化代码逻辑:

// 记录新增的引用关系,key是黑色对象,value是该黑色对象新引用的白色对象集合
Map<Object, Set<Object>> newReferenceRecords = new HashMap<>();

// 当黑色对象obj新增对whiteObj的引用时触发
public void onReferenceAdd(Object obj, Object whiteObj) {
    // 仅当obj是黑色,whiteObj是白色时才记录
    if (isBlack(obj) && isWhite(whiteObj)) {
        Set<Object> refs = newReferenceRecords.getOrDefault(obj, new HashSet<>());
        refs.add(whiteObj);
        newReferenceRecords.put(obj, refs);
    }
}

// 重新标记阶段处理记录的新引用
public void processNewReferences() {
    for (Map.Entry<Object, Set<Object>> entry : newReferenceRecords.entrySet()) {
        Object blackObj = entry.getKey();
        // 将黑色对象重新标记为灰色
        markGray(blackObj);
        // 扫描其引用的白色对象
        for (Object whiteObj : entry.getValue()) {
            markGray(whiteObj);
        }
    }
}

原始快照(Snapshot At The Beginning,SATB)

原始快照的思路是当灰色对象要删除指向白色对象的引用时,就将这个要删除的引用记录下来,并发标记结束后,再将这些记录下来的引用关系中,被删除引用的白色对象标记为灰色,保证这些对象不会被漏标。

以下是原始快照记录引用删除的简化代码逻辑:

// 记录删除的引用关系,key是灰色对象,value是该灰色对象删除引用的白色对象集合
Map<Object, Set<Object>> deletedReferenceRecords = new HashMap<>();

// 当灰色对象obj删除对whiteObj的引用时触发
public void onReferenceDelete(Object obj, Object whiteObj) {
    // 仅当obj是灰色,whiteObj是白色时才记录
    if (isGray(obj) && isWhite(whiteObj)) {
        Set<Object> refs = deletedReferenceRecords.getOrDefault(obj, new HashSet<>());
        refs.add(whiteObj);
        deletedReferenceRecords.put(obj, refs);
    }
}

// 重新标记阶段处理记录删除的引用
public void processDeletedReferences() {
    for (Map.Entry<Object, Set<Object>> entry : deletedReferenceRecords.entrySet()) {
        for (Object whiteObj : entry.getValue()) {
            // 将被删除引用的白色对象标记为灰色
            markGray(whiteObj);
        }
    }
}

两种方案的对比

两种方案各有适用场景,具体差异如下:

对比维度增量更新原始快照
核心思路关注黑色对象的新增引用关注灰色对象的引用删除
重新标记开销需要重新扫描新增引用的黑色对象及其引用链仅需要扫描被删除引用的白色对象
适用场景引用新增操作较多的场景引用删除操作较多的场景
典型应用CMS垃圾回收器的并发标记阶段G1垃圾回收器的并发标记阶段

实际应用注意事项

在实际的垃圾回收器实现中,还需要配合写屏障来捕获引用变化的操作,保证新增或删除的引用能被正确记录。同时,重新标记阶段通常是需要停顿用户线程的,因此要尽量减少重新标记的范围,降低对应用性能的影响。

如果应用中出现频繁的漏标导致的对象误回收问题,可以先排查是否是垃圾回收器的漏标处理机制没有正确开启,或者是否自定义了特殊的引用处理逻辑破坏了默认的标记规则。

三色标记算法并发标记漏标问题垃圾回收修改时间:2026-07-01 09:51:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。