参考

前言

关于三色收集和屏障技术的文章已经很多。先总结一下背景知识: 1. go使用混合屏障。删除屏障:假设A--ref-->B,ref断开时会对B染色。插入屏障:假设GC时,有新的引用C--ref2-->E,E也会被染色。 2. 上面的屏障保护只发生在堆的对象上。因为性能考虑,栈上的引用改变不会引起屏障触发。

先举个最常见例子:

  1. 初时,栈A对象引用了C对象。栈B引用了D对象。
  2. 在GC时,栈1和A对象已被扫黑。
  3. 此时解除B-->D的引用,同时,新建引用C-->D。
  4. 因为有插入屏障,D会被染色,不会被误回收。

附上代码:

大部分文章,都在这戛然而止。留下无限的遐思。

然而,我们是思考者:

  1. 为什么写屏障不保护栈的引用,为什么栈上触发写屏障就会影响性能?
  2. 如果发生栈上对象引用改变。因为不涉及屏障,为什么不会发生错误。

问题1很好解答,因为go是并发运行的,大部分的操作都发生在栈上。数十万goroutine的栈都进行屏障保护自然会有性能问题。

关键是,如果屏障不保护栈的引用,那如何保证正确性。

设想下面的场景:

  1. 故事发生在两个goroutine上
  2. 栈1已被扫黑,它下面的对象都是灰色
  3. 栈2还未被扫黑,所以它引用的D可能是白色。
  4. 在GC之时,解除ref2,并且将ref指向D。
  5. 因为引用改变都发生在栈上,不会触发屏障。所以D被回收?

因为这是一个伪命题:

  1. 对栈的操作是原子操作,要么栈全灰,要么全黑。
  2. 已被扫黑的栈,引用的堆上的对象至少是灰色。(比如C对象)。所以不可能发生同栈下引用改变会影响GC的问题。
  3. 不可能发生上述的跨栈的引用。因为“对象不是从天上掉下来的”。假设A对象可以与D对象建立引用,只有可能A也直接间接持有B对象。否则没有路径可以建立这样的引用。然而,因为Go的逃逸分析,B对象被外部引用,不可能存在于栈上。所以B一定是堆上的对象。