• Go 1.3:并行清扫,标记过程需要 STW,停顿时间在约几百毫秒

    • Go 1.6:使用 bitmap 来记录回收内存的位置,大幅优化垃圾回收器自身消耗的内存,停顿时间在十毫秒以内

    • Go 1.7:停顿时间控制在两毫秒以内

    • Go 1.12:整合了两个阶段的 Mark Termination,但引入了一个严重的 GC Bug 至今未修(见问题 20),尚无该 Bug 对 GC 性能影响的报告

    • Go 1.14:替代了仅存活了一个版本的 scavenger,全新的页分配器,优化分配内存过程的速率与现有的扩展性问题,并引入了异步抢占,解决了由于密集循环导致的 STW 时间过长的问题

    可以用下图直观地说明 GC 的演进历史:

    这一想法很自然,因为并行化导致算法结果不一致的情况仅仅发生在标记阶段,而当时的垃圾回收器没有针对并行结果的一致性进行任何优化,因此才需要在标记阶段进入 STW。对于 Scavenger 而言,早期的版本中会有一个单独的线程来定期将多余的内存归还给操作系统。

    16. Go 历史各个版本在 GC 方面的改进? - 图2

    而到了 Go 1.5 后,Go 团队花费了相当大的力气,通过引入写屏障的机制来保证算法的一致性,才得以将整个 GC 控制在很小的 STW 内,而到了 1.8 时,由于新的混合屏障的出现,消除了对栈本身的重新扫描,STW 的时间进一步缩减。

    从这个时候开始,Scavenger 已经从独立线程中移除,并合并至系统监控这个独立的线程中,并周期性地向操作系统归还内存,但仍然会有内存溢出这种比较极端的情况出现,因为程序可能在短时间内应对突发性的内存申请需求时,内存还没来得及归还操作系统,导致堆不断向操作系统申请内存,从而出现内存溢出。