深挖三色标记算法的底层原理

文章目录

目录

文章目录

前言

一、什么是三色标记算法?

二、核心概念:三种颜色的含义

三、标记过程(核心步骤)

[1. 初始标记(Initial Mark,STW)](#1. 初始标记(Initial Mark,STW))

[2. 并发标记(Concurrent Mark,非 STW)](#2. 并发标记(Concurrent Mark,非 STW))

[3. 重新标记(Remark,STW)](#3. 重新标记(Remark,STW))

[4. 清除 / 整理(Sweep/Compact,可选 STW)](#4. 清除 / 整理(Sweep/Compact,可选 STW))

四、并发标记的核心问题:如何避免漏标?

[1. 增量更新(Incremental Update)](#1. 增量更新(Incremental Update))

[2. 原始快照(Snapshot At The Beginning,SATB)](#2. 原始快照(Snapshot At The Beginning,SATB))

五、三色标记的优势

总结



前言

在JVM的垃圾回收机制中,准确识别哪些对象是垃圾(即不再被引用的对象)是核心任务之一,通常采用根可达性分析算法。
传统的垃圾回收算法如标记-清除(Mark-Sweep)、标记-整理(Mark-Compact)等在标记阶段通常会产生STW(Stop The World)方式,这会导致应用程序出现明显的停顿。为了减少这种停顿时间,JVM引入了三色标记算法,它是现代垃圾回收器(如CMS、G1、ZGC等)的核心算法之一。

一、什么是三色标记算法?

三色标记算法是一种追踪式垃圾回收(GC) 中用于标记存活对象的经典算法,其核心是通过给对象标记三种颜色(白色、灰色、黑色)来区分对象在回收过程中的状态,从而高效地识别可达对象(存活)和不可达对象(垃圾)。该算法的优势在于支持并发标记(即标记过程中应用程序可继续运行),能显著减少 GC 停顿时间。它被广泛应用于现代 GC 收集器(如 Java 的 G1、ZGC、Shenandoah,Go 的 GC 等)。

二、核心概念:三种颜色的含义

三色标记通过 "颜色" 划分对象的可达性状态,三种颜色的定义如下:

  • 白色:未被标记的对象。初始状态下所有对象都是白色,若最终仍为白色,则被判定为 "垃圾"(不可达),会被回收。
  • 灰色 :自身已被标记为 "可达",但它所引用的子对象尚未完全标记(即还需继续处理其引用关系)。灰色对象是标记过程的 "中间状态",相当于 GC 的 "待处理队列"。
  • 黑色 :自身已被标记为 "可达",且它所有直接引用的子对象都已完成标记(即该对象的引用链已处理完毕)。黑色对象是 "安全的",不会被误回收。

三、标记过程(核心步骤)

三色标记的目标是通过遍历对象引用链,将所有 "可达对象" 标记为黑色(或灰色,最终转为黑色),未标记的白色对象即为垃圾。过程可分为以下阶段:

1. 初始标记(Initial Mark,STW)
  • 目的 :标记 "根对象"(Roots)直接引用的对象。
    根对象是 GC 的起点,包括:栈帧中的局部变量、静态变量、常量池、JNI 引用等。
  • 操作
    • 暂停用户线程(STW),避免根对象引用关系被动态修改。
    • 遍历根对象,将其直接引用的对象标记为灰色(根对象本身若为对象,也会被标记)。
  • 特点:仅处理根对象的直接引用,耗时极短(通常毫秒级)。
2. 并发标记(Concurrent Mark,非 STW)
  • 目的:从灰色对象出发,递归标记所有可达对象,逐步将灰色对象转为黑色。
  • 操作
    • 恢复用户线程运行(与 GC 线程并发执行)。
    • GC 线程从 "灰色对象队列" 中取出对象,遍历其所有直接引用的子对象:
      • 若子对象是白色,将其标记为灰色(加入灰色队列)。
      • 当该对象的所有子对象都处理完毕后,将自身标记为黑色(移出灰色队列)。
  • 关键问题 :并发标记时,用户线程可能修改对象引用关系(如删除引用、新增引用),导致 "标记不一致",可能出现两种错误:
    • 漏标:一个可达对象被误判为白色(垃圾),导致被回收(严重错误)。
    • 错标:一个不可达对象被误判为黑色(可达),导致暂时不回收(轻微错误,下次 GC 可修正)。
3. 重新标记(Remark,STW)
  • 目的:修正并发标记阶段因用户线程修改引用导致的标记错误(主要解决漏标)。
  • 操作
    • 再次暂停用户线程(STW)。
    • 处理并发标记期间记录的 "引用变更日志"(通过 "写屏障" 记录,见下文),重新标记被遗漏的可达对象(将其从白色转为灰色或黑色)。
  • 特点:耗时比初始标记长,但远短于全量 STW(因只需处理变更的引用)。
4. 清除 / 整理(Sweep/Compact,可选 STW)
  • 目的:回收白色对象(垃圾),并整理存活对象(减少内存碎片)。
  • 操作
    • 清除:直接回收所有白色对象的内存。
    • 整理:将黑色对象(存活对象)移动到连续的内存区域(如 G1 的 Region 整理)。
  • 特点:部分收集器(如 ZGC)可通过 "指针碰撞" 等技术实现并发整理,进一步减少 STW。

四、并发标记的核心问题:如何避免漏标?

漏标是最严重的错误(会导致有用对象被回收),其根源是:并发标记时,一个黑色对象新增了对白色对象的引用,而该白色对象未被任何灰色对象引用(导致白色对象无法被标记)。

为解决漏标,现代 GC 引入写屏障(Write Barrier) 技术,在用户线程修改对象引用时触发特定逻辑,记录引用变更。主流方案有两种:

1. 增量更新(Incremental Update)
  • 核心思想:当 "黑色对象" 新增对 "白色对象" 的引用时,将该黑色对象 "降级" 为灰色,确保后续 GC 线程会重新处理它的引用(包括新增的白色对象)。
  • 举例
    黑色对象 A 原本引用 B(已标记),用户线程让 A 新增引用白色对象 C。此时通过写屏障将 A 重新标记为灰色,GC 线程后续会再次处理 A,发现 C 并标记为灰色。
  • 应用:Java 的 CMS 收集器。
2. 原始快照(Snapshot At The Beginning,SATB)
  • 核心思想:在并发标记开始时,记录所有 "可达对象" 的快照。当 "灰色对象" 删除对 "白色对象" 的引用时,通过写屏障保留该引用(记录被删除的白色对象),确保其在本次 GC 中被标记为可达。
  • 举例
    灰色对象 A 原本引用白色对象 C,用户线程删除 A→C 的引用。此时写屏障记录 C,GC 线程仍会将 C 标记为灰色(因它曾被可达对象引用)。
  • 优势:减少重新标记的工作量;应用:Java 的 G1 收集器。

五、三色标记的优势

  1. 减少 STW 时间:仅初始标记和重新标记需要短暂 STW,并发标记阶段用户线程可正常运行,适合对响应时间敏感的应用(如 Web 服务)。
  2. 高效追踪可达性:通过灰色对象作为 "中间状态",避免了两色标记(黑白)在并发时的标记混乱问题。
  3. 适配多种回收策略:可结合标记 - 清除、标记 - 整理等策略,灵活应对不同内存场景。

总结

三色标记是现代并发 GC 的基础,通过白、灰、黑三色划分对象状态,配合初始标记、并发标记、重新标记三个阶段,在减少 STW 的同时高效识别垃圾。其核心挑战是解决并发标记时的漏标问题,通过写屏障(增量更新、SATB)实现准确标记,最终平衡 GC 效率与系统响应性。好了,今天依旧是深蹲不写BUG,我们一起努力!!!

相关推荐
上官浩仁3 小时前
springboot knife4j 接口文档入门与实战
java·spring boot·spring
BlackPercy3 小时前
【图论】Graphs.jl 最小生成树算法文档
算法·图论
THMAIL3 小时前
机器学习从入门到精通 - Python环境搭建与Jupyter魔法:机器学习起航必备
linux·人工智能·python·算法·机器学习·docker·逻辑回归
启山智软4 小时前
商城源码后端性能优化:JVM 参数调优与内存泄漏排查实战
java·电子商务·商城开发
wuk9984 小时前
在Spring MVC中使用查询字符串与参数
java·spring·mvc
YXWik64 小时前
java 使用 spring AI 实战 RAG (Chroma 向量数据库+Advisor)
java·人工智能·spring
间彧4 小时前
Stream.collect(Collectors.toList())和Stream.toList()
java
We....4 小时前
Java集合---Collection接口和Map接口
java·开发语言
悟能不能悟4 小时前
快速排序算法详解
数据结构·算法·排序算法