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

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 能够胜任高并发、低延迟服务场景的重要保障。

相关推荐
花酒锄作田4 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
qwfys2004 天前
How to install golang 1.26.0 to Ubuntu 24.04
ubuntu·golang·install
codeejun4 天前
每日一Go-25、Go语言进阶:深入并发模式1
开发语言·后端·golang
石牌桥网管4 天前
Go 泛型(Generics)
服务器·开发语言·golang
小二·4 天前
Go 语言系统编程与云原生开发实战(第21篇)
开发语言·云原生·golang
小二·4 天前
Go 语言系统编程与云原生开发实战(第20篇)
开发语言·云原生·golang
女王大人万岁4 天前
Golang实战Eclipse Paho MQTT库:MQTT通信全解析
服务器·开发语言·后端·golang
codeejun4 天前
每日一Go-24、Go语言实战-综合项目:规划与搭建
开发语言·后端·golang
石牌桥网管4 天前
Go类型断言
开发语言·后端·golang
普通网友5 天前
PHP语言的正则表达式
开发语言·后端·golang