搞懂Go垃圾回收

本文主要介绍了垃圾回收的概念,Golang GC的垃圾回收算法和工做原理,看完本文可让你对Golang垃圾回收机制有个全面的理解。因为本人不了解其余语言的GC,并未对比其余语言的垃圾回收算法,须要的能够自行Google。html

什么是垃圾回收

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

简单地说,垃圾回收(GC)是在后台运行一个守护线程,它的做用是在监控各个对象的状态,识别而且丢弃再也不使用的对象来释放和重用资源。程序员

go的垃圾回收

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

标记-清除法(mark and sweep)

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

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

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

三色标记法

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

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

那么什么是root呢? 看了不少文章都没解释这这个概念,在这儿说明下:root区域主要是程序运行到当前时刻的栈和全局数据区域。oop

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

也能够参考下面的动图辅助理解:性能

Go GC如何工做

上面介绍的是GO GC采用的三色标记算法,可是好像并无体现出来怎么减小STW对程序的影响呢?实际上是由于Golang GC的大部分处理是和用户代码并行的学习

GC期间用户代码可能会改变某些对象的状态,如何实现GC和用户代码并行呢?先看下GC工做的完整流程:

  1. Mark: 包含两部分:
  • Mark Prepare: 初始化GC任务,包括开启写屏障(write barrier)和辅助GC(mutator assist),统计root对象的任务数量等。这个过程须要STW
  • GC Drains: 扫描全部root对象,包括全局指针和goroutine(G)栈上的指针(扫描对应G栈时需中止该G),将其加入标记队列(灰色队列),并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行
  1. Mark Termination: 完成标记工做,从新扫描(re-scan)全局指针和栈。由于Mark和用户程序是并行的,因此在Mark过程当中可能会有新的对象分配和指针赋值,这个时候就须要经过写屏障(write barrier)记录下来,re-scan 再检查一下。这个过程也是会STW的。
  2. Sweep: 按照标记结果回收全部的白色对象,该过程后台并行执行
  3. Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工做完成才能够开始新一轮的GC。 若是标记期间用户逻辑改变了刚打完标记的对象的引用状态,怎么办呢?

写屏障(Write Barrier)

写屏障:该屏障以前的写操做和以后的写操做相比,先被系统其它组件感知。 好难懂哦,结合上面GC工做的完整流程就好理解了,就是在每一轮GC开始时会初始化一个叫作“屏障”的东西,而后由它记录第一次scan时各个对象的状态,以便和第二次re-scan进行比对,引用状态变化的对象被标记为灰色以防止丢失,将屏障先后状态未变化对象继续处理。

辅助GC

从上面的GC工做的完整流程能够看出Golang GC实际上把单次暂停时间分散掉了,原本程序执⾏多是“⽤户代码-->⼤段GC-->⽤户代码”,那么分散之后实际上变成了“⽤户代码-->⼩段 GC-->⽤户代码-->⼩段GC-->⽤户代码”这样。若是GC回收的速度跟不上用户代码分配对象的速度呢? Go 语⾔若是发现扫描后回收的速度跟不上分配的速度它依然会把⽤户逻辑暂停,⽤户逻辑暂停了之后也就意味着不会有新的对象出现,同时会把⽤户线程抢过来加⼊到垃圾回收⾥⾯加快垃圾回收的速度。这样⼀来原来的并发仍是变成了STW,仍是得把⽤户线程暂停掉,要否则扫描和回收没完没了了停不下来,由于新分配对象⽐回收快,因此这种东⻄叫作辅助回收。

如何进行GC调优

衡量GC对程序的影响能够参考这篇文章,Go 程序的性能调试问题

减小对象的分配,合理重复利用; 避免string与[]byte转化;

二者发生转换的时候,底层数据结结构会进行复制,所以致使 gc 效率会变低。

少许使用+链接 string;

Go里面string是最基础的类型,是一个只读类型,针对他的每个操做都会建立一个新的string。 若是是少许小文本拼接,用 “+” 就好;若是是大量小文本拼接,用 strings.Join;若是是大量大文本拼接,用 bytes.Buffer。

GC触发条件

自动垃圾回收的触发条件有两个:

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

写在最后

虽然Golang有自动垃圾回收机制,可是GC不是万能的,最好仍是养成手动回收内存的习惯:好比手动把再也不使用的内存释放,把对象置成nil,也能够考虑在合适的时候调用runtime.GC()触发GC。

近期在维护的go学习示例代码,新入坑的朋友们能够关注下 go-programming

参考:

string讨论

Go语言——垃圾回收GC

Golang 垃圾回收剖析

Golang垃圾回收机制详解

go垃圾回收概要

常见GC算法及Golang GC

相关文章
相关标签/搜索