Go V1.3 之前的标记清除(mark and sweep)

流程

  • 第⼀步,暂停程序业务逻辑, 找出不可达的对象,和可达对象
  • 第⼆步, 开始标记,程序找出它所有可达的对象,并做上标记
  • 第三步, 标记完了之后,然后开始清除未标记的对象
  • 第四步, 停⽌暂停,让程序继续跑。然后循环重复这个过程,直到process程序⽣命周期结束

缺点

  • STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)
  • 标记需要扫描整个heap
  • 清除数据会产⽣heap碎⽚

Go V1.5 三色标记法

流程

  • 第⼀步 , 就是只要是新创建的对象,默认的颜⾊都是标记为“⽩⾊”
  • 第⼆步, 每次GC回收开始, 然后从根节点开始遍历所有对象,把遍历到的对象从⽩⾊集合放⼊“灰⾊”集合
  • 第三步, 遍历灰⾊集合,将灰⾊对象引⽤的对象从⽩⾊集合放⼊灰⾊集合,之后将此灰⾊对象放⼊⿊⾊集合
  • 第四步, 重复第三步, 直到灰⾊中⽆任何对象

  • 第五步: 回收所有的⽩⾊标记表的对象. 也就是回收垃圾

如果三色标记法不被STW保护

  • 条件1: ⼀个⽩⾊对象被⿊⾊对象引⽤(⽩⾊被挂在⿊⾊下)
  • 条件2: 灰⾊对象与它之间的可达关系的⽩⾊对象遭到破坏(灰⾊同时丢了该⽩⾊)
    两个条件同时满⾜,那么就会出现对象丢失的现象

强弱三色不变式

  • 强三⾊不变式,破坏条件1
  • 弱三⾊不变式 破坏条件2

屏障机制

插入屏障

  具体操作: 在A对象引⽤B对象的时候,B对象被标记为灰⾊。(将B挂在A下游,B必须被标记为灰⾊)
  满⾜: 强三⾊不变式. (不存在⿊⾊对象引⽤⽩⾊对象的情况了, 因为⽩⾊会强制变成灰⾊)
  A.添加下游对象(nil, B) //A 之前没有下游, 新添加⼀个下游对象B, B被标记为灰⾊
  A.添加下游对象(C, B) //A 将下游对象C 更换为B, B被标记为灰⾊
  结束时需要STW来重新扫描栈,⼤约需要10~100ms

删除屏障

  具体操作: 被删除的对象,如果⾃身为灰⾊或者⽩⾊,那么被标记为灰⾊。	
  满⾜: 弱三⾊不变式. (保护灰⾊对象到⽩⾊对象的路径不会断)
  A.添加下游对象(B, nil) //A对象,删除B对象的引⽤。 B被A删除,被标记为灰(如果B之前为⽩)
  A.添加下游对象(B, C) //A对象,更换下游B变成C。 B被A删除,被标记为灰(如果B之前为⽩)
  回收精度低,⼀个对象即使被删除了最后⼀个指向它的指针也依旧可以活过这⼀轮,在下⼀轮GC中被清理掉。

Go V1.8 混合写屏障机制

具体操作

满⾜: 变形的弱三⾊不变式. (结合了插⼊、删除写屏障两者的有点)

  • GC开始将栈上的对象全部扫描并标记为⿊⾊(之后不再进⾏第⼆次重复扫描,⽆需STW)
  • GC期间,任何在栈上创建的新对象,均为⿊⾊。
  • 被删除的对象标记为灰⾊。
  • 被添加的对象标记为灰⾊。

场景

场景1 对象被⼀个堆对象删除引用,成为栈对象的下游

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

场景1对象被⼀个栈对象删除引用,成为另⼀个栈对象的下游

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


场景3对象被⼀个堆对象删除引用,成为另⼀个堆对象的下游

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


场景四对象从⼀个栈对象删除引用,成为另⼀个堆对象的下游

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


总结

  • Go V1.3 普通的标记清除法, 整体过程需要STW,效率极低
  • Go V1.5 三⾊标记法, 对空间启动写屏障,栈空间不启动, 全部扫描之后,需要重新扫描⼀次栈(需要STW),效率普通
  • Go V1.8 三⾊标记法,混合写屏障机制, 栈空间不启动,堆空间启动, 整体过程⼏乎不需要STW, 效率较⾼