「这是我参与11月更文挑战的第 11 天,活动详情查看:2021最后一次更文挑战」
GC
基本GC算法简介:
-
标记-清理:常见的就是 三色标记法
- 黑色 → 已经被扫描
- 灰色 → 被黑色引用到,暂时没有扫描,扫描之后变为黑色
- 白色 → 暂时没有被扫描到,可能为垃圾对象;如果被灰色对象引用的对象 也都会被扫描
- 缺点:内存碎片
-
标记-压缩:
- 将分散,存活的对象移动,形成更为紧密的空间来解决内存碎片的问题
- 标记 → 类似 标记-清理算法
- 压缩 → 将活的对象压缩到空闲的区域
- 缺点:压缩过程中对象的之间引用也需要被同时迁移,增大了实现的难度
-
引用计数
基本步骤
- 标记准备
- 并行标记
- 标记终止
- 垃圾清除
先看看标记:
-
可以和用户执行的协程并行
-
扫描的第一阶段是扫描根对象
-
颜色区别:
- 灰色:该对象已经标记,对象下的属性没有完全标记
- 黑色:对象以及对象下的属性都全部被标记
- 白色:没有被标记 (对象垃圾)
内存屏障
为了解决悬挂指针的问题,引入屏障技术保障数据的一致性。分为写屏障和读屏障,GO中用的是写屏障:因为不需要对象拷贝,读屏障是没有意义的。
标记算法保证正确性,达到以下任意一共都可以:
-
强三色不变
- 黑色不会指向白色,只能指向灰色或者是黑色
-
弱三色不变
- 黑色可以指向白色
第一阶段
GC开始,STW ⇒ 初始内存都是白色,进入标记队列为灰色。
- STW
- 每一个P启动一个 mark worker G 用于标记 (用于第二阶段)
- 开启写屏障 ⇒ 记录一下后续进行marking时被修改的指针
- 找到所有roots (stack, heap, global vars),并加入标记队列
- Start the World,进入第二阶段
第二阶段
marking, start the world
- 标记队列取出对象,标记为黑色 (不能GC)
- 然后检查是否指向另一个对象,有则加入标记队列
- 看分配对象是否为指针,是则scan下一个对象;否则停止该路scan,开始队列下一个对象scan
- 在扫描过程中,如果发现用户修改对象,触发写屏障,将对象标记为灰色,并加入单独的扫描队列
需要注意的是:
- 可以和用户程序并发进行
第三阶段
处理marking中修改过的指针,STW
STW,将gc write barrier记录的修改指针加入标记队列进行新一轮标记。
需要注意:
- 这个阶段不是并行的
- 扫描完了以后start the world
第四阶段
内存管理中有个 bitmap 结构,可以标记是否为黑色
改进历程
- 最开始采用原始的标记-清理方式,整个过程需要STW
- 分离标记/清扫操作:标记过程STW,清扫并发执行
- 采用三色标记;回收
总结
- GC开始将栈上空间全部扫描并标记为黑色 (就不需要二次扫描,无需STW)
- GC中,任何在栈上创建的新对象,均为黑色
- 被删除引用的对象标记为灰色
- 增加新的引用到新对象,此对象标记为灰色