go语言GC|青训营笔记

157 阅读5分钟

Golang垃圾回收机制

GC(garbage collection)

标记清除(mark and sweep)

流程:

  1. 暂停程序业务逻辑,找出不可达的对象,和可达的对象
  2. 开始标记,程序找出它所有的可达的对象,并做上标记
  3. 标记完之后,开始清除未标记的对象
  4. 停止暂停,让程序继续跑,然后循环重复这个过程,直到process程序生命周期结束

缺点:

  1. STW(stop time world):让程序暂停,程序出现卡顿
  2. 标记需要扫描的整个heap
  3. 清除数据会产生heap碎片

*将第三步和第四步换位置,会缩短STW的范围

三色标记法

white白色 gray灰色 black黑色

流程:

  1. 只要是新创建的对象,默认的颜色都是标记为"白色"
  2. 每次GC回收开始,然后从根节点开始遍历所有对象,把遍历到的对象从白色集合放入到"灰色"集合
  3. 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
  4. 重复第三步,直到灰色中无任何对象
  5. 回收所有的白色标记表的对象,也就是回收垃圾

注意:

如果三色标记法不被STW保护,会有以下两个条件

  1. 一个白色对象被黑色对象引用(白色被挂在黑色下)
  2. 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)

若两个条件同时满足,那么就会出现对象丢失的现象。

强三色不变式:

破坏条件1

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

弱三色不变式:

破坏条件2

黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象

如果三色标记满足强弱不变式之一,即可保证不丢失对象

屏障机制

当在程序执行过程中,增加额外的判断机制,不打扰正常执行

插入屏障

不在栈上使用

对象被引用时 触发的机制

具体操作:在A对象引用B对象的时候,B对象被标记为灰色(将B挂在A下游,B必须标记为灰色)

满足:强三色不变式

伪码

// 伪码
增加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    标记灰色(新下游对象ptr)

    // 2
    当前下游对象slot = 新下游对象ptr
}

场景:

A.增加下游对象(nil,B) // A之前没有下哟,新增加一个下游对象BB被标记为灰色
A.增加下游对象(C,B)   // A将下游对象更换为BB被标记为灰色

不足:

结束时需要STW来重新扫描栈,大约10~100ms

删除屏障

对象被删除时 触发的机制

具体操作:被删除的对象,如果自身为灰色或者白色,那么标记为灰色

满足:弱三色不变式

伪码:

// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    if (当前下游对象slot是灰色 || 当前下游对象slot是白色){
        标记灰色(当前下游对想slot)    // slot为被删除对象,标记为灰色
    }
    
    // 2
    当前下游对想slot = 新下游对象ptr
}

场景:

A.增加下游对象(B,nil) // A对象,删除B对象的引用。BA删除,被标记为灰(如果B之前为白)
A.增加下游对象(B,C)   // A对象,跟换下游B变为C。bA删除,被标记为灰(如果B之前为白)

不足:

回收精度低

一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉

混合写屏障机制

具体操作:

  1. GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
  2. GC期间,任何在栈上创建的新对象,均为黑色
  3. 被删除对象被标记为灰色
  4. 被添加的对象标记为灰色

满足:变形的弱三色不变式(结合了插入、删除写屏障两者的优点)

伪码:

// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    标记灰色(当前下游对象slot)


    // 2
    标记灰色(新下游对象ptr)


    // 3
    当前下游对象slot = 新下游对象ptr
}

场景一

对象被一个堆对象删除引用,称为栈对象的下游

伪码:

// 伪码
// 前提:堆对象4->对象7 = 对象7 // 对象7 被 对象4引用
栈对象1->对象7 = 堆对象7;   // 将堆对象7 挂在 栈对象1 下游
堆对象4->对象7 = nil;   // 对象4 删除引用 对象7

场景二

对象被一个栈对象删除引用,称为另一个站对象的下游

// 伪码
new 栈对象9
对象9->对象3 = 对象3    // 将栈对象3 挂在 栈对象9 下游
对象2->对象3 = null     // 对象2 删除引用 对象3

场景三

对象被一个堆对象删除引用,成为另一个堆对象的下游

// 伪码
堆对象10->对象7 = 堆对象7   // 将堆对象7 挂在 堆对象10 下游
堆对象4->对象7 = nil        // 对象4 删除引用 对象7

场景四

对象从一个栈对象删除引用,称为另一个堆对象的下游

// 伪码
堆对象1->对象2 = nil        // 对象1 删除引用 对象2
堆对象4->对象2 = 堆对象2    // 对象4 添加 下游 栈对象2
堆对象->对象7 = nil         // 对象4 删除引用 对象7