目录

GoLang gc

非分代,非紧缩,写屏障,并发标记清理

常用gc算法

引用计数

  • c++ shared_ptr

  • 无法解决循环引用的问题

Mark-Sweep

  • 先标记出使用中的

  • 再回收未使用的

Mark-Compact

  • 相比Mark-Sweep减少了内存碎片

复制算法

  • 内存利用率太低

分代收集

  • 按生命周期将对象分为两代或多代

  • 较新的代内gc会更频繁

  • 生命周期长的对象会被转移到较老代中

三色标记

步骤:

  1. 起始所有对象都是白色

  2. 从根出发扫描到的所有可达对象,变为灰色

  3. 扫描每个灰色对象,将灰色置为黑色,并将其可达的白色对象置为灰色

  4. 重复3,直到没有灰色对象

会产生的问题和解决方法

浮动垃圾:扫描A时将B标记为灰色,随后A取消了对B的引用,B成为浮动垃圾,本次gc不会回收B。但下次gc会回收,影响不大
漏标:

情况1:扫描A时A无引用,扫描A以后用户程序将A对象新申请了B对象,此时B对象未被标记;
情况2:扫描A时A无引用,扫描A以后用户程序将A对象引用了一个还未被标记的对象B;

写屏障

满足任一三色一致性时均可保证gc不出错:

弱三色不变式:所有被黑色对象引用的白色对象都处于灰色保护状态(直接或间接从灰色对象可达)
强三色不变式:不存在黑色对象到白色对象的指针

为了执行效率,栈不启用写屏障

插入写屏障

执行指针赋值时,如果指向的内存区域为白色,且父对象为黑色,则将其标记为灰色。但只针对堆上指针写入设置写屏障,标记结束时需要针对栈上对象再扫描一次。满足强三色一致性。

删除写屏障

如果A被标记为灰色后取消了对B的引用,则会将B标记为灰色。悲观的认为B以及被B引用的对象会被黑色对象引用,回收精度较低。GC开始时需要STW获取根节点并把一级节点标灰来保护当前存活对象。满足弱三色一致性。

混合写屏障

相比插入写屏障,结束时不用再扫描栈。相比删除写屏障,开始时STW只需要扫描栈。

  1. GC开始时优先扫描将栈,将栈上可达对象标记为黑色。扫描某个 goroutine 时停止这个 goroutine 赋值器的工作。即goroutine看来是原子操作,瞬间全灰/黑。栈扫描完成后解锁。

  2. GC期间栈上新建的对象都为黑色

  3. 被添加的对象标记为灰色

  4. 被删除的对象标记为灰色

混合写屏障case分析

栈对象A引用C, 堆对象B引用D

  1. A删除C,B新加C:B新加C置为灰

  2. B删除D,A新加D:B删除D时将D置灰

  3. B删除D,放到另一个堆对象E下:删除和添加均置灰

  4. A删除C,放到另一个栈对象下:无操作。栈上对象均已标黑

优点:

  1. 标记时不用stop the world