Go语言垃圾回收机制详解:三色标记法如何避免内存泄漏

 更新时间:2026年08月15日 11:54:11   作者:学习同学  
本文深入讲解Go语言垃圾回收机制,从Go1.3的标记清除到Go1.8的混合写屏障,详细分析三色标记法原理、异常情况及屏障机制,帮助你理解如何高效避免内存泄漏并减少STW停顿

垃圾回收机制

在C++中 我们在学习内存管理的时候被强调了无数次new出来的资源一定要delete掉 不然就会造成一个内存泄漏的问题

但是在Go语言中 由于有了垃圾回收机制的存在 避免了大部分的内存泄漏问题 所以说我们不用担心new出来的资源需要释放的问题

那么什么是垃圾回收机制呢?

垃圾回收(Garbage Collection, GC)是一种内存管理机制 用于自动回收程序中不再被使用的内存空间 防止内存泄漏 垃圾回收机制主要应用于高级编程语言 如Java、Python、Go等 它们通常拥有内置的垃圾回收器 垃圾回收机制的主要原理是识别哪些内存是“无法访问”的 即没有任何引用指向该内存的对象 然后释放这些内存以供再次使用

Go语言中的垃圾回收机制也在不停的进步 今天我们主要介绍下面三个版本

  • Go 1.3版本:普通标记清除法,整体过程需要启动暂停程序执行(Stop-The-World),效率极低
  • Go 1.5版本: 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要暂停程序执行(Stop-The-World)),效率普通
  • Go 1.8版本:三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要暂停程序执行(Stop-The-World),效率较高。

Go1.3 标记清除法

在Go语言的1.3版本之前 GC主要使用的是标记清除法 (mark and sweep) 标记清除法主要有两个步骤

  • 标记
  • 清除

该算法的运行步骤如下

  1. 暂定业务逻辑
  2. 找到程序中所有可达和不可达的对象
  3. 将所有的可达对象坐上标记
  4. 将未标记对象清除
  5. 恢复程序的业务逻辑
  6. 重复上面五步 直到程序的生命周期结束

下面我们通过几张图来演示下上面的过程

首先这是一个程序运行图

其中的箭头指向标识引用 比如说对象1指向了对象2 则说明对象2被对象1引用

之后我们开始暂停整个程序 并且开始找出并且标记所有可达的对象

标记完毕之后 我们开始清除所有不可达的对象

最后让程序重新运行即可

这种方法有两个缺点

  • 性能开销:在标记和清除过程中 需要暂停程序执行(Stop-The-World) 这会导致程序性能的短暂下降 特别是对于大型应用或者堆内存较大的情况下 这种影响会更加明显
  • 内存碎片化:标记-清除算法会在堆内存中留下许多小的不连续空闲区域 即内存碎片 这使得分配大块连续内存变得困难 可能导致额外的性能开销或者提前触发下一次垃圾回收 甚至在极端情况下可能导致内存分配错误 即使堆内存中还有足够的总空间

三色标记法

在Go语言1.5版本中引入了三色标记法

三色标记法的出现主要是为了减少程序暂停时间(以下简称STW) 或者不使用STW时间

三色标记法是一种用于垃圾回收的算法 特别是在并发垃圾回收器中常见

它使用三种颜色来标记对象 分别是:白色、灰色和黑色 这些颜色用来表示对象的状态 以决定它们是否可以被回收 该方法通常与增量或并发垃圾回收结合使用以减少程序运行时的暂停时间

在三色标记法中 对象根据它们的访问状态被染成以下颜色

  • 白色:这些是尚未被访问的对象 在算法开始时 除了根对象 所有的对象都会被标记为白色
  • 灰色:这些对象已经被标记(意味着它们是存活的) 但是还没有检查它们的子对象 灰色对象代表了还未处理完的工作
  • 黑色:这些对象已经被标记且它们的所有子对象也都已经被访问过了 黑色对象表示没有更多的工作需要对它们进行处理

三色标记法的工作流程通常如下:

  1. 初始化:所有的对象初始为白色 根集合中的对象被标记为灰色
  2. 标记:每一步迭代中 算法都会取出一个灰色对象 检查它所有的子对象 如果子对象是白色 那么它们将被标记为灰色 表示它们是存活的但还没有被完全处理 处理过的对象会变成黑色
  3. 扫描:这个过程重复进行 直到没有更多的灰色对象为止
  4. 清理:在没有灰色对象后 所有的白色对象都被视为垃圾 并可以被安全地回收 因为它们不可达

使用三色标记法的垃圾回收器涉及到复杂的同步和安全点设置 以确保在并发执行时的正确性

例如 为了处理在并发标记过程中可能发生的对象状态变化 可能需要使用写屏障(write barrier)来记录任何白色到黑色的引用变化 以避免丢失这些可能的新引用

下面我们还是通过一组图来带大家熟悉下流程

当程序创建时 将所有的对象标记为白色 并且放到白色标记表中

这一步很简单 但是将所有的白色对象放到白色标记表的这个过程需要STW

我们找出根节点集合

什么是根节点集合

一组特殊的对象 它们是垃圾回收算法开始搜索存活对象的起点 垃圾回收器假定这些根对象是存活的 因为它们是应用程序直接引用的 并从这些对象开始遍历所有可达的对象

这些对象通常是以下几种

  • 栈上的引用:局部变量和输入参数的引用 包括当前执行线程的方法调用栈中的引用
  • 全局/静态引用:全局变量或静态字段中的引用 这些在全局数据区域或静态存储区域
  • 寄存器中的引用:某些情况下 虚拟机或运行时环境可能会把对象引用存储在CPU寄存器中

这些根节点构成了对象图遍历的初始点 任何从这些根节点开始通过引用可以达到的对象 都被认为是存活的 不应该被垃圾回收

之后我们开始从根节点开始遍历所有的白色节点 并且将遍历到的所有白色对象染灰

之后我们开始遍历灰色对象集合 将灰色对象的引用染灰之后 将原本的灰色对象染黑

之后重复上面的步骤 直到灰色集合中无对象为止

最后我们将所有的白色集合中的对象回收

单纯三色标记的异常情况

我们上面所讲解的三色标记法一定是依赖于STW的 因为如果不STW的话 可能会造成下面的情况

在程序运行一开始 本来是对象2的指针指向对象3的 可是当对象2进入灰色标记表之后删除了这个指针 此时对象4用了一个指针指向对象3

这也就导致在最后对象3会被当成垃圾给回收了

通过对于上述流程的分析我们可以发现这两种情况是三色标记法最不希望发生的

  • 一个白色对象被黑色对象所引用
  • 灰色对象与它之间的可达关系的白色对象遭到破坏

如果说这两种情况同时发生的话 那么白色对象就可能会被当作垃圾回收掉 那么我们应该如何解决这个问题呢?

最简单的解决方法就是STW了 可是如果我们要使用STW解决这个问题 不如直接使用标记清除法了

那么为了解决这个问题的同时又尽量减少STW时间 我们这里引入了屏障机制

通过我们上面对于异常流程的分析 我们可以知道 只有达成两个异常条件才会触发这种异常情况 那么我们是不是只要破坏掉其中一个条件就可以了啊

于是我们就可以使用强弱三色不变式来解决这个问题

强三色不变式

强制性不允许黑色对象引用白色对象

弱三色不变式

所有被黑色对象引用的白色对象都处于灰色保护状态

通过这两个不变式我们可以引入插入屏障和删除屏障

插入屏障

插入屏障主要是利用了强三色不等式

举个例子 比如说对象A为黑色 此时我们要新增一个对象B到堆中 并且由对象A引用 那么此时对象B就不会是白色 而是会被自动染色成灰色

下面我们还是通过几张图来梳理下流程

前面几步和普通的三色标记法没有什么区别

到下图 我们插入了两个对象 (对象8和对象9)

由于堆上开启了插入屏障 所以说对象8会被自动染灰

而由于栈上没有开启插入屏障 所以说对象9依然是白色

这样子就避免了堆上STW的同时保证了白色对象不会被误删

到这里有的同学可能会有疑问了 那么栈上的对象9呢 它依然是白色 并且被黑色对象引用了 那么它是不是要被误删了

为了防止栈上对象被误删 我们在第一次三色标记法之后会对于栈上开启STW保护来避免这个问题

为什么不在栈上开启插入屏障

  • 栈区的生命周期通常是相对固定的 他们的生命周期和作用域紧密相连 这种自动 确定的生命周期让插入屏障不必要
  • 栈高效的内存操作 栈遵循后进先出 这个操作模式极为高效 引入插入屏障会降低性能

删除写屏障

删除写屏障主要是利用了弱三色不等式

举个例子 比如说对象A为白色 我们此时删除了对象A 那么此时对象A就会变为灰色 以满足弱三色不等式

下面我们通过几张图来梳理下流程

前两步和前面一样这里不过多讲解

此时我们要删除对象1对于对象5的引用 这里就会触发删除写屏障 对象5变成灰色并且放入灰色标记表中

之后继续三色标记法的步骤即可 最后会变成这样

这里有的同学可能会产生疑问 对象2 3 5 不应该是垃圾嘛? 为什么这一轮GC没有被回收掉 这里刚好可以引出删除写屏障的特点

  • 回收精度低 一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮 在下一轮被回收
  • 需要STW暂停 栈里面的对象虽然不多 但是依旧需要STW 影响效率

混合写屏障机制

混合写屏障机制的提出主要是为了前面版本的三色标记法的两个缺点

  • 栈区需要STW
  • 回收精度低 需要二次GC

Go V1.8版本引入了混合写屏障机制(hybrid write barrier) 避免了对栈re-scan的过程 极大的减少了STW的时间 结合了两者的优点

下面是混合写屏障的运行流程

  • 将栈上的所有可达对象全部标记为黑色
  • GC期间任何在栈上创建的对象均为黑色
  • 被删除引用的对象均标记为灰色
  • 被添加引用的对象均标记为灰色
  • 栈不开启屏障机制

它主要是利用了弱三色不等式

我们下面来分析下这种方式的可行性

第一步 我们将栈上的所有可达对象标记为黑色

下面我们通过四种情况来论证混合写屏障机制的正确性

堆上对象被删除引用 并且被栈上对象引用

此时对象7由于被删除被标记为灰色 之后被对象1引用 没有错误

情况2 对象被一个栈上的对象删除 并被另外一个栈对象引用

9是栈上新创建的对象 此时变为黑色 之后对象2删除对对象3的引用 之后对象9指向对象3 对象3变为灰色 最后还是会变成黑色 没有问题

情况3 4同情况1 2 这里就不再赘述

总结下

混合写屏障(Hybrid Write Barrier)本身就是一种结合了插入屏障(Insertion Barrier)和删除屏障(Deletion Barrier)功能的屏障机制 它的目的是在运行时根据具体情况选择性地使用插入和/或删除屏障的逻辑 以优化性能并减少对程序运行的影响

使用了混合写屏障之后几乎不需要STW 效率极高

关于混合写屏障一次GC不能清除所有垃圾的问题

这个暂时没有办法解决 实际上开发人员也考虑到了这一点 再第二次GC时 上一次没有被处理的垃圾就会被回收了

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Go语言中程序是怎么编译的实现

    Go语言中程序是怎么编译的实现

    本文主要介绍了Go语言中程序是怎么编译的实现,深入探讨Go语言的编译机制和最新的模块管理系统Go Modules的使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • 一文详解Golang如何解决内存溢出

    一文详解Golang如何解决内存溢出

    内存溢出是指程序在运行时超出了分配给它的内存限制,从而导致程序异常或崩溃的现象,本文主要为大家介绍了Golang解决内存溢出的方法,感兴趣的小伙伴可以了解下
    2025-01-01
  • Gorm存在时更新,不存在时创建的问题

    Gorm存在时更新,不存在时创建的问题

    这篇文章主要介绍了Gorm存在时更新,不存在时创建的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • golang频率限制 rate详解

    golang频率限制 rate详解

    这篇文章主要介绍了golang频率限制 rate详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • go语言切片slice使用细节和注意事项整理大全

    go语言切片slice使用细节和注意事项整理大全

    这篇文章主要给大家介绍了关于go语言切片slice使用细节和注意事项整理的相关资料,需要的朋友可以参考下
    2024-05-05
  • 简单聊聊为什么说Go语言字符串是不可变的

    简单聊聊为什么说Go语言字符串是不可变的

    最近有读者留言说,平时在写代码的过程中,是会对字符串进行修改的,但网上都说 Go 语言字符串是不可变的,这是为什么呢,本文就来和大家简单讲讲
    2023-05-05
  • Go实现HTTP请求转发的示例代码

    Go实现HTTP请求转发的示例代码

    请求转发是一项核心且常见的功能,本文主要介绍了Go实现HTTP请求转发的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-05-05
  • goland 清除所有的默认设置操作

    goland 清除所有的默认设置操作

    这篇文章主要介绍了goland 清除所有的默认设置操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Go语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步
    2021-11-11
  • Golang通脉之map详情

    Golang通脉之map详情

    这篇文章主要介绍了Golang通脉之map,Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现,map 是一种无序的键值对的集合。map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值 map 是一种集合,所以可以像迭代数组和切片那样迭代它
    2021-10-10

最新评论