2026/4/6 5:58:48
网站建设
项目流程
网站设计的人员分工,自己做的网站怎么发布到网上,沧州网站艰涩很,一个人做网站用什么技术摘要#xff1a;面试中#xff0c;GC#xff08;Garbage Collection#xff09;永远是那座绕不过去的大山。死记硬背概念往往经不起面试官的深问。本文拒绝纸上谈兵#xff0c;将带你用 Go 语言从零手写一个基于“三色标记法”的简易垃圾回收器。通过代码实战#xff0c;…摘要面试中GCGarbage 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 定义对象模型packagemainimportfmt// Color 代表三色标记的状态typeColorintconst(White ColoriotaGrey 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,}GlobalHeapappend(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.Reqsappend(objA.Reqs,objB)// 返回根节点集合return[]*Object{objA}}3.3 实现三色标记器funcGC(roots[]*Object){fmt.Println( GC Start )// 1. 初始化根节点入灰色栈greySet:make([]*Object,0)for_,root:rangeroots{root.ColorGrey greySetappend(greySet,root)fmt.Printf(Mark Grey: %s\n,root.Value)}// 2. 标记循环forlen(greySet)0{// Pop 一个灰色对象current:greySet[0]greySetgreySet[1:]// 模拟队列fmt.Printf(Processing: %s\n,current.Value)// 扫描子对象for_,ref:rangecurrent.Reqs{ifref.ColorWhite{ref.ColorGrey greySetappend(greySet,ref)fmt.Printf( - Mark Child Grey: %s\n,ref.Value)}}// 当前对象处理完毕标黑current.ColorBlack fmt.Printf(Mark Black: %s\n,current.Value)}// 3. 清除 (Sweep)sweep()fmt.Println( GC End )}funcsweep(){newHeap:make([]*Object,0)for_,obj:rangeGlobalHeap{ifobj.ColorWhite{fmt.Printf(♻️ Collecting Garbage: %s\n,obj.Value)// 真实场景下这里会释放内存}else{// 存活对象重置颜色为 White 供下一轮 GC 使用obj.ColorWhite newHeapappend(newHeap,obj)}}GlobalHeapnewHeap}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 标记期间修改了引用怎么办场景GC 扫描完 A (黑)A 此时指向 nil。B (灰) 指向 C (白)。用户代码执行A.ref C(黑指向白)B.ref nil(断开灰指向白)。如果不加以干预GC 会认为 A 已经扫完了不再看B 也没引用了。结果C (白色)就会被误删这就是严重的悬挂指针问题。解决方案Dijkstra 插入写屏障在对象建立引用时A.ref C强制把 C 染灰破坏“黑指向白”的条件。// 模拟写屏障funcWriteBarrier(slot*Object,ptr*Object){// 强制把下游对象染灰ifptr.ColorWhite{ptr.ColorGrey// 加入灰色队列...}*slot*ptr}Go V1.8 引入的混合写屏障 (Hybrid Write Barrier)结合了 Dijkstra 和 Yuasa 屏障的优点极大地减少了 STW 时间。5. 总结通过不到 100 行代码我们还原了三色标记法的核心骨架。虽然真实的 Go GC 包含极其复杂的调度、内存分配器tcmalloc和位图标记但万变不离其宗。掌握了这个模型你就掌握了通向 GC 内核的钥匙。互动话题你在面试中遇到过哪些奇葩的 GC 问题欢迎在评论区留言