「Go GC」学习要点总结

213 阅读3分钟

「这是我参与11月更文挑战的第 11 天,活动详情查看:2021最后一次更文挑战


GC

基本GC算法简介:

  1. 标记-清理:常见的就是 三色标记法

    1. 黑色 → 已经被扫描
    2. 灰色 → 被黑色引用到,暂时没有扫描,扫描之后变为黑色
    3. 白色 → 暂时没有被扫描到,可能为垃圾对象;如果被灰色对象引用的对象 也都会被扫描
    4. 缺点:内存碎片
  2. 标记-压缩:

    1. 将分散,存活的对象移动,形成更为紧密的空间来解决内存碎片的问题
    2. 标记 → 类似 标记-清理算法
    3. 压缩 → 将活的对象压缩到空闲的区域
    4. 缺点:压缩过程中对象的之间引用也需要被同时迁移,增大了实现的难度
  3. 引用计数

基本步骤

  1. 标记准备
  2. 并行标记
  3. 标记终止
  4. 垃圾清除

先看看标记:

  1. 可以和用户执行的协程并行

  2. 扫描的第一阶段是扫描根对象

  3. 颜色区别:

    1. 灰色:该对象已经标记,对象下的属性没有完全标记
    2. 黑色:对象以及对象下的属性都全部被标记
    3. 白色:没有被标记 (对象垃圾)

内存屏障

为了解决悬挂指针的问题,引入屏障技术保障数据的一致性。分为写屏障和读屏障,GO中用的是写屏障:因为不需要对象拷贝,读屏障是没有意义的。

标记算法保证正确性,达到以下任意一共都可以:

  1. 强三色不变

    • 黑色不会指向白色,只能指向灰色或者是黑色
  2. 弱三色不变

    • 黑色可以指向白色

第一阶段

GC开始,STW ⇒ 初始内存都是白色,进入标记队列为灰色。

  1. STW
  2. 每一个P启动一个 mark worker G 用于标记 (用于第二阶段)
  3. 开启写屏障 ⇒ 记录一下后续进行marking时被修改的指针
  4. 找到所有roots (stack, heap, global vars),并加入标记队列
  5. Start the World,进入第二阶段

第二阶段

marking, start the world

  1. 标记队列取出对象,标记为黑色 (不能GC)
  2. 然后检查是否指向另一个对象,有则加入标记队列
  3. 看分配对象是否为指针,是则scan下一个对象;否则停止该路scan,开始队列下一个对象scan
  4. 在扫描过程中,如果发现用户修改对象,触发写屏障,将对象标记为灰色,并加入单独的扫描队列

需要注意的是:

  1. 可以和用户程序并发进行

第三阶段

处理marking中修改过的指针,STW

STW,将gc write barrier记录的修改指针加入标记队列进行新一轮标记。

需要注意:

  1. 这个阶段不是并行的
  2. 扫描完了以后start the world

第四阶段

内存管理中有个 bitmap 结构,可以标记是否为黑色

改进历程

  1. 最开始采用原始的标记-清理方式,整个过程需要STW
  2. 分离标记/清扫操作:标记过程STW,清扫并发执行
  3. 采用三色标记;回收

总结

  1. GC开始将栈上空间全部扫描并标记为黑色 (就不需要二次扫描,无需STW)
  2. GC中,任何在栈上创建的新对象,均为黑色
  3. 被删除引用的对象标记为灰色
  4. 增加新的引用到新对象,此对象标记为灰色