news 2026/4/30 10:30:52

垃圾回收 (GC) 手写实战:从零实现一个“三色标记法”的 Go 语言简易 GC

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
垃圾回收 (GC) 手写实战:从零实现一个“三色标记法”的 Go 语言简易 GC

摘要:面试中,GC(Garbage Collection)永远是那座绕不过去的大山。死记硬背概念往往经不起面试官的深问。本文拒绝纸上谈兵,将带你用 Go 语言从零手写一个基于“三色标记法”的简易垃圾回收器。通过代码实战,彻底降维打击面试中最晦涩的 GC 难点。

1. 为什么需要手写 GC?

很多同学对 GC 的理解停留在:“引用计数”、“标记清除”、“三色标记”这些名词上。但如果不亲手写一遍,你很难真正理解:

  • 写屏障 (Write Barrier)到底是在哪里、什么时机插入的?
  • STW (Stop The World)是为了解决什么并发安全问题?
  • 白色、灰色、黑色对象在内存中到底是如何流转的?

核心收益

  • 深度理解:从“背八股文”进阶到“上帝视角”俯视 GC 原理。
  • 面试通杀:当面试官问你 GC 时,你可以说:“我曾经手写过一个简易的并发 GC…”

2. 核心原理:三色标记法 (Tri-color Marking)

三色标记法是 CMS 和 G1 等现代垃圾回收器的理论基础,Go 语言的 GC 也是基于此改进的(无分代)。

2.1 三种颜色定义

  • ⬜️ 白色 (White):潜在的垃圾。GC 开始时,所有对象都是白色。GC 结束时,如果您还在白色集合中,那就该被回收了。
  • ⬜️ 灰色 (Grey):活跃对象,但子对象还没扫描完。这是“波面”,是黑与白之间的缓冲区。
  • ⬛️ 黑色 (Black):活跃对象,且子对象已扫描完。GC 扫描过程中,黑色对象不会再指向白色对象(除非在并发标记期间发生了指针变动,这时候就需要写屏障)。

2.2 算法流程可视化

标记循环

GC 开始

所有对象置为白色

扫描根节点

根可达对象标记为灰色

灰色集合为空?

取出一个灰色对象

将其标记为黑色

扫描其引用的子对象

子对象若为白 -> 变灰

清除所有白色对象

GC 结束

3. Go 语言代码实战

我们将简化内存模型,用一个Object结构体模拟对象,用Heap模拟堆内存。

3.1 定义对象模型

packagemainimport"fmt"// Color 代表三色标记的状态typeColorintconst(White Color=iotaGrey Black)// Object 模拟堆上的对象typeObjectstruct{Reqs[]*Object// 引用其他对象Color Color// 当前颜色Valuestring// 对象调试名}// GlobalHeap 模拟堆空间varGlobalHeap[]*Object// NewObject 分配一个对象funcNewObject(namestring)*Object{obj:=&Object{Reqs:make([]*Object,0),Color:White,// 初始都是白色Value:name,}GlobalHeap=append(GlobalHeap,obj)returnobj}

3.2 模拟引用关系

构造一个经典的引用链:Root -> A -> B,以及一个孤立的垃圾对象C

funcBuildGraph()[]*Object{// 创建对象objA:=NewObject("ObjA")objB:=NewObject("ObjB")objC:=NewObject("ObjC")// 这里的 C 就是垃圾// 建立引用关系:Root -> A -> B// 我们假设 main 函数返回的就是 Root Set (根集合)objA.Reqs=append(objA.Reqs,objB)// 返回根节点集合return[]*Object{objA}}

3.3 实现三色标记器

funcGC(roots[]*Object){fmt.Println("=== GC Start ===")// 1. 初始化:根节点入灰色栈greySet:=make([]*Object,0)for_,root:=rangeroots{root.Color=Grey greySet=append(greySet,root)fmt.Printf("Mark Grey: %s\n",root.Value)}// 2. 标记循环forlen(greySet)>0{// Pop 一个灰色对象current:=greySet[0]greySet=greySet[1:]// 模拟队列fmt.Printf("Processing: %s\n",current.Value)// 扫描子对象for_,ref:=rangecurrent.Reqs{ifref.Color==White{ref.Color=Grey greySet=append(greySet,ref)fmt.Printf(" -> Mark Child Grey: %s\n",ref.Value)}}// 当前对象处理完毕,标黑current.Color=Black fmt.Printf("Mark Black: %s\n",current.Value)}// 3. 清除 (Sweep)sweep()fmt.Println("=== GC End ===")}funcsweep(){newHeap:=make([]*Object,0)for_,obj:=rangeGlobalHeap{ifobj.Color==White{fmt.Printf("♻️ Collecting Garbage: %s\n",obj.Value)// 真实场景下这里会释放内存}else{// 存活对象,重置颜色为 White 供下一轮 GC 使用obj.Color=White newHeap=append(newHeap,obj)}}GlobalHeap=newHeap}

3.4 完整运行与验证

funcmain(){roots:=BuildGraph()fmt.Println("Before GC, Heap Size:",len(GlobalHeap))GC(roots)fmt.Println("After GC, Heap Size:",len(GlobalHeap))}

运行结果预期

  • ObjA (Root) 变灰 -> 变黑
  • ObjB (被 A 引用) 变灰 -> 变黑
  • ObjC (无引用) 保持白色 ->被回收

4. 深度解析:写屏障 (Write Barrier)

在上述代码中,我们是一个单线程的 STW GC。但 Go 的 GC 是并发运行的。如果用户代码(Mutator)在 GC 标记期间修改了引用怎么办?

场景

  1. GC 扫描完 A (黑),A 此时指向 nil。
  2. B (灰) 指向 C (白)。
  3. 用户代码执行:A.ref = C(黑指向白),B.ref = nil(断开灰指向白)。

如果不加以干预,GC 会认为 A 已经扫完了(不再看),B 也没引用了。结果C (白色)就会被误删!这就是严重的悬挂指针问题。

解决方案:Dijkstra 插入写屏障

在对象建立引用时(A.ref = C),强制把 C 染灰,破坏“黑指向白”的条件。

// 模拟写屏障funcWriteBarrier(slot*Object,ptr*Object){// 强制把下游对象染灰ifptr.Color==White{ptr.Color=Grey// 加入灰色队列...}*slot=*ptr}

Go V1.8 引入的混合写屏障 (Hybrid Write Barrier)结合了 Dijkstra 和 Yuasa 屏障的优点,极大地减少了 STW 时间。

5. 总结

通过不到 100 行代码,我们还原了三色标记法的核心骨架。虽然真实的 Go GC 包含极其复杂的调度、内存分配器(tcmalloc)和位图标记,但万变不离其宗。掌握了这个模型,你就掌握了通向 GC 内核的钥匙。


互动话题:你在面试中遇到过哪些奇葩的 GC 问题?欢迎在评论区留言!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 4:45:21

(3-3)机器人身体结构与人体仿生学:四肢结构设计原则

3.3 四肢结构设计原则四肢是人形机器人实现运动执行、负载作业与人机交互的核心执行单元,其设计需围绕“运动灵活性、承载可靠性、轻量化集成”三大核心目标,平衡关节运动范围、驱动效率与力传递性能。3.3.1 手臂结构:肩、肘、腕的解耦设计…

作者头像 李华
网站建设 2026/5/1 4:43:24

OpenHarmony + RN:SWR乐观更新实现

OpenHarmony RN:SWR乐观更新实现深度解析🔍 本文将在OpenHarmony平台上深度剖析React Native的SWR数据请求库,重点讲解乐观更新机制的核心实现原理、OpenHarmony特定适配方案以及性能优化策略。通过完整可运行的代码示例和架构对比&#xff…

作者头像 李华
网站建设 2026/5/1 4:51:30

网页端JAVA如何处理国防项目中的大文件断点续传?

大三党毕业设计救星:10G大文件上传加密断点续传(原生JSSpringBoot) 兄弟,作为山西某高校计科专业的大三老狗,我太懂你现在的处境了——毕业设计要做文件管理系统,甲方(老师)要10G大…

作者头像 李华
网站建设 2026/4/30 20:27:00

计算机毕业设计|基于springboot + vue驾校管理系统(源码+数据库+文档)

驾校管理 目录 基于springboot vue驾校管理系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue驾校管理系统 一、前言 博主介绍:✌️大…

作者头像 李华
网站建设 2026/5/1 4:51:22

从手动管理到智能运营:Citrix许可证管理数字化升级

从手动管理到智能运营:Citrix许可证管理数字化升级作为一名长期从事Citrix平台运维的工程师,我深知企业IT部门在进行许可证管理时所面临的挑战。是在大规模部署的环境下,手动登记、核对、更新许可证不仅效率低下,更存在极高的出错…

作者头像 李华
网站建设 2026/5/1 4:56:12

基于Python个人财务管理系统(源码+数据库+文档)

个人财务管理 目录 基于PythonDjango个人财务管理系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于PythonDjango个人财务管理系统 一、前言 博主介绍&#xff1a…

作者头像 李华