GC

三色标记法过程

  1. 开始前所有对象都是白色
  2. 将根节点指向的对象标记成灰色
  3. 遍历每个灰色对象,将自己指向的对象标成灰色,同时将自己标成黑色
  4. 重复至没有灰色对象
  5. 清除剩余的所有白色对象

写屏障

因为标记过程是并发的,在并发阶段新创建的对象如何处理着色?

  1. 白色,不行,万一引用这个新对象的是个黑色,那在这一轮标记结束后,该对象会被清除,可能会引起用户程序错误
  2. 灰色,可行,染成灰色是偏保守但不会出错的方案,但如果这个对象实际上不是存活对象的话,这轮gc是肯定不会清除他的,只能等下一轮gc
  3. 黑色,不行,万一新对象引用了一个白对象,该白对象就可能不会被扫描到,该白对象会被清除,可能会引起用户程序错误 (存疑?这么说对不对?)

完整过程

标记准备

  1. stw
  2. 所有处理器都会进入安全点

标记

  1. 将gc状态改为_GCmark,在堆上开启写屏障,栈上不启用,将根对象扫描任务入队(包括栈对象、全局对象以及不在堆中的运行时数据结构)
  2. 恢复程序执行(/usr/local/go/src/runtime/mgc.go:1381)
  3. 遍历根节点扫描任务队列,扫描goroutine栈时会导致goroutine停止,并将栈上的所有指针置灰(/usr/local/go/src/runtime/mgcmark.go:1485)
  4. 遍历灰色队列,将灰色置为黑色,并将其指向的对象置灰
  5. 分布式终止算法检测是否标记完毕

标记结束

  1. stw,将gc状态改为_GCmarktermination
  2. 关闭工作线程、协助线程

清除

  1. 将gc状态改为_GCoff,初始化清理状态并关闭写屏障(/usr/local/go/src/runtime/mgc.go:280 setGCPhase)
  2. 恢复程序执行
  3. 后台并发清理所有内存管理单元,在申请内存时才会惰性回收内存

stop the world -> 修改屏障 -> start the world

混合写屏障

特点:

  1. 写屏障只应用在堆上启用,栈上不启用。
  2. GC期间新分配的对象为黑色。
  3. 被删除和被添加的对象均标记为灰色。

好处:

  • 降低了标记终止状态的stw时间

关于协助线程

默认gc会遵从用户设置的gc消耗cpu上限,但如果用户内存分配过快,导致gc不过来时,gc会通过抢占goroutine来主动抢占cpu使用量,选择goroutine的原则是:谁分配的快就抢占谁

触发时机

后台触发

runtime会在程序初始化时开启一个goroutine用来定时强制触发gc

用户手动触发

用户手动调用runtime.GC()

申请内存时触发

申请内存时根据堆大小触发垃圾收集