11 3
go垃圾回收概要

内存回收的源头是垃圾清理操作

之所以说回收而非释放,是因为整个内存分配器的核心是内存复用,不再使用的内存会被放回合适的位置,等下次分配时再次使用。只有当空闲内存资源过多时,才会考虑释放。

垃圾回收器:是一种动态存储分配器,它自动释放程序不再需要的已分配块(垃圾);它可以被视为一个和应用并行的线程,不断更新可达图

John McCarthy

约翰·麦卡锡 在1959年前后为了去掉Lisp中的手动回收机制而发明了自动垃圾回收。随着时代的进步,目前很多语言已经支持自动的垃圾回收机制,包括:

  • Lisp
  • Python
  • Java
  • Perl
  • Ruby
  • Haskell
  • Go

Go 的垃圾回收方式

Go1.5开始采用三色-标记-清除的垃圾回收方式进行GC处理 (三色标记的目的,主要是用于做增量的垃圾回收)

3ms

标记清除算法把存储器视为可达图, 可达图中的根节点对应于不在堆的位置,这些位置包括:

  • 寄存器
  • 虚拟机存储器中读写数据区域内的全局变量

Go GC的基本特征

  • 非分代
  • 非紧缩
  • 写屏障
  • 并发标记

go采用三色标记和写屏障:

  • 起初所有的对象都是白色
  • 扫描找出所有可达对象,标记为灰色,放入待处理队列
  • 从队列提取灰色对象,将其引用对象标记为灰色放入队列
  • 写屏障监视对象的内存修改,重新标色或放回队列

关于go的写屏障(write barrier),可以阅读最近一篇比较热的文章《Proposal: Eliminate STW stack re-scanning》。 作者主要介绍下个版本Go为了消除STW所做的一些改进,包括写屏障的优化方式。

并发的三色标记算法是一个经典算法,通过write barrier,维护”黑色对象不能引用白色对象”这条约束,就可以保证程序的正确性。Go1.5会在标记阶段开启write barrier。在这个阶段里,如果用户代码想要执行操作,修改一个黑色对象去引用白色对象,则write barrier代码直接将该白色对象置为灰色。去读源代码实现的时候,有一个很小的细节:原版的算法中只是黑色引用白色则需要将白色标记,而Go1.5实现中是不管黑色/灰色/白色对象,只要引用了白色对象,就将这个白色对象标记。这么做的原因是,Go的标记位图跟对象本身的内存是在不同的地方,无法原子性地进行修改,而采用一些线程同步的实现代价又较高,所以这里的算法做过一些变种的处理。

go回收的几个阶段

  • GCoff 垃圾回收关闭状态
  • GCscan 扫描阶段
  • GCmark 标记阶段,write barrier生效
  • GCmarktermination 标记结束阶段,STW,分配黑色对象
  • GCsweep 清扫阶段

gc

标记核心

  • 抑制堆增长
  • 充分利用CPU资源
某些时候,对象的分配速度可能快于后台标记,所以要想办法做到分配和回收的平衡

Go1.5的设计目标是,尽量缩短STW(stop the world)的时间,提高应用程序的实时性。不stop the world,意味着垃圾回收过程将和用户代码同时运行 为了让进程处于良性的状态,Go启动了辅助回收:让用户代码线程参与回收,并通过mallocgc检查垃圾回收触发条件。

GC控制器

go的回收控制器可以参考mgc.go的代码实现。所谓控制器就是全程参与并发回收任务,记录相关状态数据。
为了平衡cpu的资源占用,go采用的是动态调整策略,它把工作具体划分为以下的过程:

  • 初始化:重点设置 gcpercentnext_gc
  • 启动:mallocgc会在启动前检查垃圾回收的触发条件
  • 扫描/标记: 交由MarkWorker去完成
  • 清理

标记由多个MarkWorker共同完成;这些mark worker的工作方式有三种:

  • 全力运行
  • 参与标记任务,但可被抢占和调度
  • 仅在空闲时参与标记
处于灰色对象时,无须知道真实大小,只当做内存分配器object块就可以了

C语言的标记清除手机去必须是保守的,其根本原因是C语言不会用类型信息来标记存储位置, 例如:

  • 像int或者float这样的标量可以伪装成指针
  • 没有办法判断这个数据是int而不是指针

所以C里面必须使用保守的方式来把对象标记为可达。
而Go标记可达的工作方式是 指针类型进行长度对齐+bitmap (这样就可以找出所有引用成员)。

使用bitmap的目的是为了让copy-on-write兼容,达到快速清除。因为GC中标记清除在使用过程中会逐渐产生细化的分块,不久后,会导致无数的小分块散布在堆的各处。 也就是我们常说的碎片化, 为了降低碎片,Go使用了bitmap作为辅助。

Go GC 时机

垃圾回收的触发是由一个gcpercent的变量控制的

当新分配的内存占已在使用中的内存的比例超过gcprecent时就会触发。

比如,gcpercent=100,当前使用了4M的内存,那么当内存分配到达8M时就会再次gc。如果回收完毕后,内存的使用量为5M,那么下次回收的时机则是内存分配达到10M的时候。也就是说,并不是内存分配越多,垃圾回收频率越高。

资料