常见的GC算法

引用计数法

根据对象自身引用计数来判断是否被回收,当自身引用数为0时,对象会被回收

  • 优点:简单直接,回收速度快
  • 缺点:每个对象都要维护一个自身的引用数,需要额外的开销。

标记清除法

标记出所有不需要回收的对象,清除未标记的对象。

  • 优点:简单直接,速度快。
  • 缺点:造成内存浪费,可能出现总内存是够的,但是内存不连续。

Golang的三色标记

三色标记法,将对象标记成三个颜色

  • 白色:可能被清除的,没有被回收器访问到的对象。
  • 灰色:已经被回收器扫描到的对象,但是可能指向了一些白色对象,所以回收器还要根据指向的指针遍历白色对象。
  • 黑色:已经被回收器扫描到的对象,并且所指向的一级对象也被扫描到。

三色标记的过程

1:所有的对象开始全为白色,回收器从根节点进行扫描,将扫描到的对象变为灰色。

2:扫描灰色对象的集合,将扫描到的灰色对象指向的白色对象变为灰色,并将扫描到的灰色对象变为黑色。

3:重复执行上一步,直到灰色对象全变为黑色对象,并将白色对象清理。

根对象

什么叫根对象?刚开始我以为是一些定义的基本变量。

根对象包含的内容:

1:全局变量:程序在编译期就能确定存在于整个执行的生命周期的变量
2:执行栈:每个协程都会有自己的栈,变量指针等。
3:寄存器:可能存储的是指针或者其他用到的变量。

STW

在回收器执行时,将代码暂停运行,执行回收操作,但是这种方法存在的问题是,回收器启动之后,代码暂停,导致运行效率低。

no STW

如果在回收器执行时,不将代码暂停运行,可能会导致,灰色或者黑的标记指向白色的标记,这也导致误清除。
如果在回收器执行时,不将代码暂停运行,也可能导致,黑色的标记指向的灰色标记释放,回收器执行之后,并没有清除这个。导致清理不干净。

解决方法

  • 写屏障
    灰色赋值器
    在回收器执行时,所有新创建的内容,要被标记成灰色。

在赋值之前,先将对象进行染色,再指向该对象。

写屏障过程:

  1. 在正常的收集过程中,如果堆和栈上都指向了新的对象的情况下。
  2. 在堆中的对象指向新的对象后,新的对象会先被标记为灰色对象,再指向新的对象。
  3. 在栈中的对象指向新的对象后,会直接指向新的对象,在着色完成之后,开启stw,重新着色栈中的对象。

缺点:

1:过于保守。原来被指向的对象,可能没有被引用,并没有判断是否引用,没有被清理。
2:开销过大。在执行回收器时,所有创建对象都要执行写屏障。

  • 删除屏障

在染色时,如果一个对象修改了指向的对象,导致被指向的对象没有其他对象被指向,但是着色已经将其着色为黑色或者灰色,就会导致该对象在本轮gc时,并没有被清除。

缺点

  1. 造成一些没有被使用的对象在本轮gc没有被清除,可能会造成浪费。

Golang之gc

在清理之前,需要先打开写屏障,收集器将等待所有的goroutine调用函数,等待函数调用是为了让goroutine停在安全的状态下,如果一些goroutine有一个for的循环,在go1.14之后,goroutine能被异步抢占。

一旦启动成功,垃圾收集器就会开始标记阶段,将堆上从根节点进行遍历。

遍历结束之后,将会启动各种清理任务。

遍历结束之后,还会启动stw,关闭写屏障。

也就是在gc的整个过程中,会启动两次stw,第一次在标记开始之前,启动写屏障,第二次在标记结束之后,会重新扫描栈中的对象,并且关闭写屏障。

什么时候会触发这个操作呢?

主动触发,调用函数。
被动触发,每隔一段时间、内存占比。