# 8.11 过去、现在与未来
## 8.14.1 被采纳的方案
我们现在来详细回顾一下 Go 中 GC 在各个版本上的演进历史。
### Go 1:朴素标记清扫
在 Go 1 的时代,尽管所有的用户代码都是并发执行的,但是一旦垃圾回收器开始进行垃圾回收工作时,所有的用户代码都会停止执行,而且垃圾回收器仅在一个线程上执行,这时是最原始的垃圾回收器的实现,即单线程版的三色标记清扫。
### Go 1.1, 1.3:并行清扫与精准标记
在 Go 1.3 时候,官方将三色标记清扫算法的垃圾回收代码改为并行,从而缩短了用户代码的停止时间,但是这仍然会造成大量的空隙,如果用户代码是一个 Web 应用,且正在处理一个非常重要的请求,则会对请求延迟造成巨大的影响。
![](https://golang.design/under-the-hood/assets/gc1.png)**图 1: Go 1 与 1.3 的垃圾回收器**
### Go 1.5:并发回收
为了解决 STW 问题,官方在 Go 1.5 开始使用 Dijkstra 写屏障技术让垃圾回收与用户代码得以并行执行。 从而只有在执行写屏障和很短一段时间内才需要进行 STW。
### Go 1.6:状态化回收器与位图技术
TODO:
### Go 1.7:独立栈收缩
TODO:
### Go 1.8, 1.9:混合写屏障
Go 团队在 1.8 引入混合屏障将 STW 进一步缩短,几乎解决了 STW 的问题。
![](https://golang.design/under-the-hood/assets/gc2.png)**图 1: Go 1.5 与 1.8 的垃圾回收器**
### Go 1.10, 1.11:双限度机制
### Go 1.12:终止奇点
### Go 1.13:清道夫
### Go 1.14:页分配器
到了 Go 1.14,由于页分配器的引入,向操作系统归还内存的操作页完全得到并发。
![](https://golang.design/under-the-hood/assets/gc3.png)**图 1: Go 1.13 与 1.14 的垃圾回收器**
## 8.14.2 被抛弃的方案
### 并发栈重扫
早期的 Go 选择了在 STW 期间,重新对栈进行扫描。 垃圾回收器首先在 GC 循环开始时扫描所有栈从而收集根。 但是如果没有栈的写屏障,我们便无法确保堆栈以后不会包含对白色对象的引用, 所以扫描栈只有黑色,直到其 Goroutine 再次执行, 因此它保守地恢复为灰色。从而在循环结束时, 垃圾回收器必须重新扫描灰色堆栈以使其变黑并完成标记任何剩余堆指针。 由于必须保证栈在此期间不会继续更改,因此重新扫描过程在 STW 时发生。 实践表明,栈的重扫需要消耗 10 - 100 毫秒的时间。
因为存在重扫的问题,除了引入混合屏障来消除重扫这一过程外, 有另一种做法可以提高重扫过程的性能,那就是将重扫的过程并发执行, 然而这一方案并没有得以实现。原因很简单:实现过程相比引入混合屏障而言十分复杂, 而且引入混合屏障能够消除重扫这一过程,将简化垃圾回收的步骤。
### ROC
请求制导式回收器可谓成也假设败也假设。请求制导假设非常符合直觉,但在实现上,由于垃圾回收器必须确保是否有 Goroutine 私有指针被写入公共对象,因此写屏障必须一直打开,这也就产生了该方法的致命缺点:昂贵的写屏障及其带来的缓存未命中,这也是这一设计最终没有被采用的主要原因。
### 传统分代 GC
实践证明请求制导回收器性能并没有带来显著提升之后,作为备选方案,Go 团队还尝试了实现传统的分代式 GC。但最终同样发现分代假设并不适用于 Go 的运行栈机制,年轻代对象在栈上就已经死亡,扫描本就该回收的执行栈并没有为由于分代假设带来明显的性能提升,也成为了这一方案最终没有被采用的主要原因。
## 8.14.3 展望
### 标记辅助时间过长
### 清扫时间过长
### 大规模场景下性能低下
### 对齐导致的内存浪费
### 可能的进化方向
- 第一部分 :基础篇
- 第1章 Go语言的前世今生
- 1.2 Go语言综述
- 1.3 顺序进程通讯
- 1.4 Plan9汇编语言
- 第2章 程序生命周期
- 2.1 从go命令谈起
- 2.2 Go程序编译流程
- 2.3 Go 程序启动引导
- 2.4 主Goroutine的生与死
- 第3 章 语言核心
- 3.1 数组.切片与字符串
- 3.2 散列表
- 3.3 函数调用
- 3.4 延迟语句
- 3.5 恐慌与恢复内建函数
- 3.6 通信原语
- 3.7 接口
- 3.8 运行时类型系统
- 3.9 类型别名
- 3.10 进一步阅读的参考文献
- 第4章 错误
- 4.1 问题的演化
- 4.2 错误值检查
- 4.3 错误格式与上下文
- 4.4 错误语义
- 4.5 错误处理的未来
- 4.6 进一步阅读的参考文献
- 第5章 同步模式
- 5.1 共享内存式同步模式
- 5.2 互斥锁
- 5.3 原子操作
- 5.4 条件变量
- 5.5 同步组
- 5.6 缓存池
- 5.7 并发安全散列表
- 5.8 上下文
- 5.9 内存一致模型
- 5.10 进一步阅读的文献参考
- 第二部分 运行时篇
- 第6章 并发调度
- 6.1 随机调度的基本概念
- 6.2 工作窃取式调度
- 6.3 MPG模型与并发调度单
- 6.4 调度循环
- 6.5 线程管理
- 6.6 信号处理机制
- 6.7 执行栈管理
- 6.8 协作与抢占
- 6.9 系统监控
- 6.10 网络轮询器
- 6.11 计时器
- 6.12 非均匀访存下的调度模型
- 6.13 进一步阅读的参考文献
- 第7章 内存分配
- 7.1 设计原则
- 7.2 组件
- 7.3 初始化
- 7.4 大对象分配
- 7.5 小对象分配
- 7.6 微对象分配
- 7.7 页分配器
- 7.8 内存统计
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 写屏幕技术
- 8.3 调步模型与强弱触发边界
- 8.4 扫描标记与标记辅助
- 8.5 免清扫式位图技术
- 8.6 前进保障与终止检测
- 8.7 安全点分析
- 8.8 分代假设与代际回收
- 8.9 请求假设与实务制导回收
- 8.10 终结器
- 8.11 过去,现在与未来
- 8.12 垃圾回收统一理论
- 8.13 进一步阅读的参考文献
- 第三部分 工具链篇
- 第9章 代码分析
- 9.1 死锁检测
- 9.2 竞争检测
- 9.3 性能追踪
- 9.4 代码测试
- 9.5 基准测试
- 9.6 运行时统计量
- 9.7 语言服务协议
- 第10章 依赖管理
- 10.1 依赖管理的难点
- 10.2 语义化版本管理
- 10.3 最小版本选择算法
- 10.4 Vgo 与dep之争
- 第12章 泛型
- 12.1 泛型设计的演进
- 12.2 基于合约的泛型
- 12.3 类型检查技术
- 12.4 泛型的未来
- 12.5 进一步阅读的的参考文献
- 第13章 编译技术
- 13.1 词法与文法
- 13.2 中间表示
- 13.3 优化器
- 13.4 指针检查器
- 13.5 逃逸分析
- 13.6 自举
- 13.7 链接器
- 13.8 汇编器
- 13.9 调用规约
- 13.10 cgo与系统调用
- 结束语: Go去向何方?