go gc垃圾回收原理

163 阅读8分钟

图示Golang垃圾回收机制 - 知乎 (zhihu.com) 搞懂Go垃圾回收 - 掘金 (juejin.cn) 一文搞懂go gc垃圾回收原理 - 掘金 (juejin.cn)

垃圾回收概念

程序创建对象等引用类型实体时会在虚拟内存中分配给它们一块内存空间,如果该内存空间不再被任何引用变量引用时就成为需要被回收的垃圾。 操作系统会记录一个进程运行时的所占用的内存、CPU和寄存器等资源,当进程结束后便由操作系统能够自动回收资源。但是对于一个运行较长时间的程序,如果使用完内存资源后没有及时释放就会造成内存泄漏甚至系统错误。

简单地说,垃圾回收(GC)是在后台运行一个守护线程,它的作用是在监控各个对象的状态,识别并且丢弃不再使用的对象来释放和重用资源。

垃圾回收目标

垃圾回收器主要包括三个目标:

  • 无内存泄漏:垃圾回收器最基本的目标就是减少防止程序员未及时释放导致的内存泄漏,垃圾回收器会识别并清理内存中的垃圾
  • 自动回收无用内存:垃圾回收器作为独立的子任务,不需要程序员显式调用即可自动清理内存垃圾
  • 内存整理:如果只是简单回收无用内存,那么堆上的内存空间会存在较多碎片而无法满足分配较大对象的需求,因此垃圾回收器需要重整内存空间,提高内存利用率

如何识别垃圾

1.引用计数(reference counting)

引用计数Reference counting会为每个对象维护一个计数器,当该对象被其他对象引用时加一,引用失效时减一,当引用次数归零后即可回收对象。

优点:

  1. 方式简单,回收速度快。

缺点:

  1. 需要额外的空间存放计数。
  2. 无法处理循环引用(如a.b=b; b.a=a)。
  3. 频繁更新引用计数降低了性能。

2.追踪式回收算法(Tracing)

追踪式算法(可达性分析)的核心思想是判断一个对象是否可达,如果这个对象一旦不可达就可以立刻被GC回收了。

有点:

  1. 解决了循环引用的问题
  2. 占用的空间少了

和引用计数法相比,有以下缺点:

  1. 无法立刻识别出垃圾对象,需要依赖GC线程
  2. 算法在标记时必须暂停整个程序,即STW(stop the world),否则其他线程有可能会修改对象的状态从而回收不该回收的对象

垃圾回收的方法

1.标记-清除算法

标记-清除Mark-Sweep算法是最基础的追踪式算法,分为 “标记”和“清除” 两个步骤

  • 标记:从根对象出发查找并标记堆中所有存活的对象;
  • 清除:遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表。

2.标记-复制算法

标记-复制Mark-Copy算法将内存分成大小相同的两块,当某一块的内存使用完了之后就将使用中的对象挨个复制到另一块内存中,最后将当前内存恢复未使用的状态。

3.标记-整理算法

在标记可回收的对象后,将所有存活的对象压缩到内存的一端,使他们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。

标记-整理Mark-Compact算法综合了标记-清除法和标记-复制法的优势,既不会产生内存碎片化的问题,也不会有一半内存空间浪费的问题。 该方法首先标记出所有“可达”的对象,然后将存活的对象移动到内存空间的一端,最后清理掉端边界以外的内存。

go的垃圾回收

原始的标记清除法先 STP(Stop The World) ,暂停整个程序的全部运行线程,将被引用的对象打上标记。

这样做有个很大的问题就是要通过STW保证GC期间标记对象的状态不能变化,整个程序都要暂停掉,在外部看来程序就会卡顿。

为了能够异步执行,减少 STW 的时间,Go 语言采用了三色标记法。

Golang GC的大部分处理是和用户代码并行的

三色标记法

三色标记法是对标记阶段的改进,原理如下: 类似 bfs ,三色标记算法将程序中的对象分成白色、黑色和灰色三类。

  • 白色:不确定对象 - 潜在的垃圾,表示还未搜索到的对象
  • 灰色:存活对象,子对象待处理,表示正在搜索还未搜索完的对象
  • 黑色:存活对象 - 活跃(可达)的对象,表示搜索完成的对象
  1. 初始状态所有对象都是白色。
  2. 从 root 根(root区域主要是程序运行到当前时刻的栈和全局数据区域)出发扫描所有根对象(下图a,b),将他们引用的对象标记为灰色
  3. 分析灰色对象是否引用了其他对象。如果没有引用其它对象则将该灰色对象标记为黑色;如果有引用则将它变为黑色的同时将它引用的对象也变为灰色
  4. 重复步骤3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。

存在的问题:

  1. 造成一些遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了,可以删除。
  2. 因为 go 中并发执行的问题,对象指针发生了改变,而颜色没变。

解决方法——屏障技术:

Go 使用了内存屏障技术,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码,类似于一个钩子。

垃圾收集器使用了写屏障(Write Barrier)技术,当对象新增或更新时,会将其着色为灰色。这样即使与用户程序并发执行,对象的引用发生改变时,垃圾收集器也能正确处理了。

执行周期

一次完整的 GC 分为四个阶段:

  • 1)标记准备(Mark Setup,需 STW),打开写屏障(Write Barrier)
  • 2)使用三色标记法标记(Marking, 并发)
  • 3)标记结束(Mark Termination,需 STW),关闭写屏障。
  • 4)清理(Sweeping, 并发)

GC触发时机

满足触发垃圾收集的基本条件:

  • 允许垃圾收集
  • 程序没有崩溃
  • 没有处于垃圾循环;

总结

垃圾回收(GC)是在后台运行一个守护线程,它的作用是在监控各个对象的状态,识别并且丢弃不再使用的对象来释放和重用资源。

主要包括三个目标:无内存泄漏自动回收无用内存内存整理

原始的标记清除法先 STP(Stop The World) ,暂停整个程序的全部运行线程,将被引用的对象打上标记。

这样做有个很大的问题就是要通过STW保证GC期间标记对象的状态不能变化,整个程序都要暂停掉,在外部看来程序就会卡顿。


为了能够异步执行,减少 STW 的时间,Go 语言采用了三色标记法。

Golang GC的大部分处理是和用户代码并行的

三色标记算法将程序中的对象分成白色、黑色和灰色三类。

  • 白色:不确定对象 - 潜在的垃圾,表示还未搜索到的对象
  • 灰色:存活对象,子对象待处理,表示正在搜索还未搜索完的对象
  • 黑色:存活对象 - 活跃(可达)的对象,表示搜索完成的对象

算法步骤

  1. 初始状态所有对象都是白色。
  2. 从 root 根出发扫描所有根对象,将他们引用的对象标记为灰色
  3. 分析灰色对象是否引用了其他对象。 如果没有引用其它对象则将该灰色对象标记为黑色;如果有引用则将它变为黑色的同时将它引用的对象也变为灰色
  4. 重复步骤3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。

三色标记算法会造成一些问题:

  1. 遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了,可以删除。
  2. 因为 go 中并发执行的问题,对象指针发生了改变,而颜色没变

Go 使用了内存屏障技术,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码,类似于一个钩子。

垃圾收集器使用了写屏障(Write Barrier)技术,当对象新增或更新时,会将其着色为灰色。这样即使与用户程序并发执行,对象的引用发生改变时,垃圾收集器也能正确处理了。