深入 Go 底层原理(六):垃圾回收(GC)

发布于:2025-08-04 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. 引言

Go 语言自带垃圾回收(Garbage Collection, GC),让开发者从手动管理内存的繁重任务中解脱出来。Go 的 GC 以其低延迟并发性而闻名,其目标是在不长时间暂停(Stop The World, STW)整个程序的情况下完成大部分回收工作。

本文将深入探讨 Go GC 的核心算法——并发三色标记清除法,以及其关键技术——写屏障(Write Barrier)。

2. GC 的目标与挑战
  • 目标:回收堆上不再被使用的对象(垃圾),并将内存返还给分配器。

  • 挑战:如何在不影响程序正常运行(即低 STW 时间)的前提下,准确、高效地完成回收?这需要在“mutator”(用户 Goroutine,会修改对象引用关系)和“collector”(GC Goroutine)之间进行精妙的协调。

3. 核心算法:并发三色标记-清除 (Tri-color Mark-and-Sweep)

Go GC 采用的是三色标记清除算法,它将堆上的对象分为三种颜色:

  • 白色 (White):对象的初始状态,代表可能是垃圾。在 GC 周期结束时,所有仍然是白色的对象都将被回收。

  • 灰色 (Grey):对象本身已被标记为存活,但其引用的其他对象(其子对象)还没有被扫描。灰色对象是待处理任务的集合。

  • 黑色 (Black):对象本身和其引用的所有子对象都已被扫描,是确认的存活对象。

GC 流程分为四个主要阶段

  1. Mark Setup (STW):

    • 这是一个短暂的 STW 阶段(通常在微秒级别)。

    • 主要任务是开启写屏障 (Write Barrier),并准备标记工作。

    • 将所有全局变量和每个 Goroutine 栈上的对象(根对象)放入灰色集合。

  2. Marking (Concurrent):

    • 这是 GC 的主要工作阶段,与用户 Goroutine 并发执行

    • GC Goroutine 会从灰色集合中取出一个对象,将其标记为黑色

    • 然后扫描该对象的所有指针字段,将其引用的所有白色对象标记为灰色,并放入灰色集合。

    • 这个过程会一直持续,直到灰色集合为空。

  3. Mark Termination (STW):

    • 这是另一个短暂的 STW 阶段。

    • 主要任务是处理一些在并发标记阶段中被写屏障捕获的、可能被遗漏的指针修改,并关闭写屏障。

  4. Sweeping (Concurrent):

    • 此阶段也与用户 Goroutine 并发执行

    • GC 会遍历堆中的所有 mspan,回收所有仍然是白色对象的内存块,并将其返还给内存分配器。

4. 关键技术:写屏障 (Write Barrier)

在并发标记阶段,如果用户 Goroutine(mutator)修改了对象的引用关系,可能会破坏三色标记的不变性,导致本应存活的对象被错误回收。

危险场景:一个黑色对象引用了一个白色对象,同时该白色对象的所有其他灰色父引用被移除了。如果不加干预,这个白色对象将永远不会被扫描,最终被当成垃圾回收。

黑色对象 -> 白色对象

为了防止这种情况,Go 引入了写屏障。写屏障是编译器插入的一小段代码,它会“拦截”所有在堆上的指针写操作。

混合写屏障 (Hybrid Write Barrier, Go 1.8+): Go 的混合写屏障结合了两种屏障的优点,其核心思想是:

  • 它保护的是白色对象:不允许黑色对象直接引用白色对象。

  • 工作机制:当 *slot = ptr(一个指针写操作)发生时,如果 ptr 指向一个白色对象,写屏障会ptr 指向的对象涂成灰色

通过这种方式,任何可能被黑色对象引用的白色对象都会被“拯救”回来,加入灰色集合,从而保证了 GC 的正确性。

5. GC 的触发时机

GC 主要由以下条件触发:

  1. 内存分配阈值 (GOGC): 当自上次 GC 以来新分配的内存达到一个阈值时,会自动触发新的 GC。这个阈值由环境变量 GOGC 控制(默认为 100),表示当堆大小增长 100% 时触发。

  2. 定时触发: runtime.sysmon 线程会定期检查,如果距离上次 GC 超过一定时间(默认为 2 分钟),会强制触发一次 GC。

  3. 手动触发: 开发者可以调用 runtime.GC() 来手动触发一次 GC。

6. 总结

Go GC 是一个低延迟、高并发的垃圾回收系统。它通过三色标记清除算法实现了大部分工作的并发执行,通过短暂的 STW 完成必要的同步,并通过混合写屏障技术保证了在用户 Goroutine 并发修改对象引用时的正确性。这一系列精巧的设计,是 Go 能够胜任高并发、低延迟服务场景的重要保障。


网站公告

今日签到

点亮在社区的每一天
去签到