什么是垃圾回收?

垃圾回收(英语:Garbage Collection,缩写为GC),在计算机科学中是一种自动的存储器管理机制。当一个计算机上的动态存储器不再需要时,就应该予以释放,以让出存储器,这种存储器资源管理,称为垃圾回收。垃圾回收器可以让程序员减轻许多负担,也减少程序员犯错的机会。

golang的垃圾回收

当前Golang使用的垃圾回收机制是三色标记发配合写屏障辅助GC,三色标记法是标记-清除法的一种增强版本。

标记-清除法(mark and sweep)

原始的标记清楚法分为两个步骤:

  1. 标记。先STP(Stop The World),暂停整个程序的全部运行线程,将被引用的对象打上标记
  2. 清除没有被打标机的对象,即回收内存资源,然后恢复运行线程。

这样做有个很大的问题就是要通过STW保证GC期间标记对象的状态不能变化,整个程序都要暂停掉,在外部看来程序就会卡顿。

三色标记法

三色标记法是对标记阶段的改进,原理如下:

  1. 初始状态所有对象都是白色。
  2. 从root根出发扫描所有根对象(下图a,b),将他们引用的对象标记为灰色(图中A,B)

root区域主要是程序运行到当前时刻的栈和全局数据区域。

  1. 分析灰色对象是否引用了其他对象。如果没有引用其它对象则将该灰色对象标记为黑色(上图中A);如果有引用则将它变为黑色的同时将它引用的对象也变为灰色(上图中B引用了D)
  2. 重复步骤3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。

Go GC如何工作

Golang GC的大部分处理是和用户代码并行的

GC工作的完整流程:

  1. Mark: 包含两部分:

    • Mark Prepare: 初始化GC任务,包括开启写屏障(write barrier)和辅助GC(mutator assist),统计root对象的任务数量等。这个过程需要STW
    • GC Drains: 扫描所有root对象,包括全局指针和goroutine(G)栈上的指针(扫描对应G栈时需停止该G),将其加入标记队列(灰色队列),并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行
  2. Mark Termination: 完成标记工作,重新扫描(re-scan)全局指针和栈。因为Mark和用户程序是并行的,所以在Mark过程中可能会有新的对象分配和指针赋值,这个时候就需要通过写屏障(write barrier)记录下来,re-scan 再检查一下。这个过程也是会STW的。

  3. Sweep: 按照标记结果回收所有的白色对象,该过程后台并行执行

  4. Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC。 如果标记期间用户逻辑改变了刚打完标记的对象的引用状态,怎么办呢?

写屏障(Write Barrier)

写屏障:该屏障之前的写操作和之后的写操作相比,先被系统其它组件感知。

在每一轮GC开始时会初始化一个叫做“屏障”的东西,然后由它记录第一次scan时各个对象的状态,以便和第二次re-scan进行比对,引用状态变化的对象被标记为灰色以防止丢失,将屏障前后状态未变化对象继续处理。

GC触发条件
  1. 超过内存大小阈值
  2. 达到定时时间 阈值是由一个gcpercent的变量控制的,当新分配的内存占已在使用中的内存的比例超过gcprecent时就会触发。比如一次回收完毕后,内存的使用量为5M,那么下次回收的时机则是内存分配达到10M的时候。也就是说,并不是内存分配越多,垃圾回收频率越高。 如果一直达不到内存大小的阈值呢?这个时候GC就会被定时时间触发,比如一直达不到10M,那就定时(默认2min触发一次)触发一次GC保证资源的回收。