Skip to content

垃圾回收

垃圾回收是许多编程语言中提供的自动内存管理机制,无须程序员手动执行。自动释放不需要的对象,让出存储器资源。

现代Golang中的垃圾回收主要应用三色标记法,在最初的标记清除法垃圾回收中需要一定时间的STW(stop the world)。为避免在回收过程中,goroutine对对象的引用做出了修改,导致垃圾回收出错。后面会根据时间顺序介绍Golang使用过的垃圾回收机制。

Go1.3 标记清除法

  1. 开启STW,停止程序运行
  2. 收集全局对象和栈上对象,进行标记。
  3. 递归标记收集到的对象,直到标记所有可达对象
  4. 停止STW,降低垃圾回收对程序运行效率的影响
  5. 清除未标记对象

G1.5三色标记法

三色是指白,灰,黑三色。白色表示暂无对象引用的潜在垃圾;灰色表示黑色到白色的中间状态,表示有黑色对象引用,但是未扫描全部子对象,垃圾收集器会扫描这些对象的子对象;黑色对象表示活跃的对象,且已经扫描完成。

  1. 所有对象标记为白色
  2. 标记堆空间可达变量为灰色,并且放入集合
  3. 遍历灰色对象集合,将遍历到的白色对象标记为灰色,并加入集合。
  4. 直到灰色对象集合为空,开启STW(保证灰色对象集合为空),扫描栈空间,可回收所有白色对象

这个过程是不是前面标记清楚法相同。

但是使用三色标记法不会STW,会将GC和程序放在一起执行。

在垃圾回收中,偶然漏掉需回收的对象并没有多少影响。但是,如果回收程序存在引用的对象。将会直接引起空指针异常,程序崩溃。

分析根源,主要是因为GC过程中出现了下面这两种情况

  1. 一个白色对象被黑色对象引用

    程序在GC中将指针从灰色对象或白色对象移动到黑色对象上,导致被扫描遗漏

  2. 灰色对象与它之间的可达关系的白色对象遭到破坏

    程序修改破坏引用后没有其他路径会扫描该对象

为了破坏这两个条件,拓展出了俩种方法,强三色不变式和弱三色不变式

  1. 强三色不变式:不允许黑色对象引用白色对象(破坏上方条件1)
  2. 弱三色不变式:黑色对象可以引用白色,白色对象存在其他灰色对象对他的引用,或者他的链路上存在灰色对象(破坏上方条件2)

在运行过程中,使用屏障机制防止出错。在这个版本,为了运行效率。屏障只对堆内存扫描时启用,在扫描栈上内存是在GC结束后开启STW重新扫描。

插入屏障:对象被引用时触发,当白色对象被黑色对象引用时,白色对象被标记为灰色(栈上对象无插入屏障)。

删除屏障:对象被删除时触发的机制。如果灰色对象引用的白色对象被删除时,那么白色对象会被标记为灰色。

这两种屏障开启一个即可保证回收不出错

Go1.8三色标记+混合写屏障

混合写屏障为以下四点

  1. GC开始时将栈上可达对象全部标记为黑色
  2. GC期间,任何栈上创建的新对象均为黑色
  3. 被删除引用对象标记为灰色
  4. 被添加引用的对象标记为黑色

当GC开始,一共分为这几步

  1. GC刚开始,默认都为白色
  2. 优先扫描栈区,将可达对象全部标记为黑
  3. 栈空间不启动屏障机制,堆空间启动。

内存申请与释放

go的垃圾回收触发有两个条件

内存占用翻倍(如初始10MB,在达到20MB的时候会触发),或者到2min主动触发。

在Linux中,向系统交还内存时通常采用懒汉式策略。不是立刻回收。而是标记内存已经不需要使用了,在系统内存紧张的时候才会真正回收。这样在程序需要重新申请内存的时候能够获得极快的分配速度。

关于变量使用指针传递还是值传递

在实际编码中,不需要过度关心性能问题,应该按照函数是否应该能够修改入参来选择值传递还是指针传递。

与C++不同。在Golang中,使用指针传递可能使原本分配于栈空间的变量分配到堆空间。加大内存和垃圾回收的压力。使用值传递大对象,可能需要较长的时间完成内存拷贝。

以下是常见的内存优化方法

对于频繁申请和销毁的小对象,可以使用对象池管理对象。避免产生大量需要回收的垃圾对象。

对于体积特别庞大的对象(拷贝较为耗时的对象)。可以考虑使用指针代替值传递,来避免内存拷贝,优化运行速度。