golang中的GC
Golang中GC主要特点:
1.并发:Golang的GC可以与程序同时运行,不会阻塞程序的执行。当进行GC时,Golang会将所有goroutine暂停,在GC结束后才会恢复执行。
具体来说,Golang的GC具备以下两个特点:
- 小内存分配:Golang将堆划分为多个固定大小的区域(span),每个区域可以包含若干个对象。当进行小内存分配时,Golang会优先尝试从不满的区域中分配内存,而不是等待GC周期结束后再进行清理。这样做可以减少GC的工作量,同时也可以保证程序更加响应快速。
- 并发标记:在标记阶段,Golang的GC采用的是并发标记(Concurrent Mark)技术。在标记过程中,GC会遍历堆中的活动对象,并记录活动对象所占用的内存块,同时还需要记录从其他对象到达该活动对象的路径(称为“Roots”)。默认情况下,Golang的GC每隔100ms就会查看当前系统负载,如果负载过高,则将并发标记的协程暂停,以避免影响系统的正常运行,否则将继续执行标记的操作。
因此,虽然Golang的GC会在某些时候暂停所有goroutine,但是Golang通过小内存分配和并发标记的技术,可以在尽可能短的时间内完成GC操作,从而保证程序响应速度。
- 分代:Golang的GC使用了分代垃圾回收算法,将堆空间划分为多个代,每个代具有不同的生命周期。年轻代(young generation)用于存放新生成的对象,并且会更加频繁地调用GC。老年代(old generation)用于存放生命周期较长的对象,相对稳定性较高,GC次数较少。
- 标记-清除:Golang的GC采用标记-清除(Mark-Sweep)算法,遍历堆中的所有内存块,标记出可达的活动对象,然后将未被标记的对象进行回收。但这种方式可能会导致碎片问题,影响内存的利用效率。
- 压缩:为了解决碎片问题,Golang还结合了压缩技术,即在回收内存时尽可能将所有存活的对象移动到一起,从而减少内存碎片和浪费。为了实现压缩技术,GC需要频繁地把活跃的内存块复制到另一个区域中进行整理。
ps:STW(stop the world)业务暂停(时间越短越好)
Go V1.3之前的:mark and sweep标记清除
步骤(采用三个阶段的策略)
- 标记阶段:STW,从根对象(根对象为:程序中已知的不需要被回收的对象,例如全局变量和函数调用栈上的临时对象)开始,递归遍历找出可达的对象,标记为活动对象
- 清扫阶段:垃圾回收器会遍历堆中的所有对象,判断哪些对象已经死亡,并将其内存释放。这个过程并不是线性的,而是通过链表等数据结构实现的。
- 整理阶段:垃圾回收器会把剩余的活动对象移动到堆的开始位置,从而使得堆空间更加连续,减少碎片,并释放未使用的内存,以便再利用。
缺点
- 在Golang v1.3之前的垃圾回收策略中,垃圾回收器对程序的执行会造成明显的停顿,在对大数据集和高并发场景下可能会导致性能问题。
- 标记需要扫描整个heap
Go V1.5 三色标记法
三色标记法的基本思想是将堆中对象看成白色、灰色、黑色三种颜色:
- 白色:表示未被访问过的内存块。
- 灰色:表示已被访问但是其子节点未被访问的内存块。
- 黑色:表示已经被访问且其子节点也已经被访问过的内存块。
标记步骤
- 初始状态,所有的对象都是白色的。
- 从根节点出发(单纯遍历一次,无递归),将所有能到达的结点染为灰色,并加入待处理队列中。
- 在队列中逐个处理每一个灰色节点。如果当前节点存在子节点,则将其子节点染为灰色并加入队列中;否则该节点变为黑色。
- 最终,所有不可达节点就会被染成白色(垃圾)。
不使用STW出现的问题:在业务持续运行下,标记为黑色的对象可能会新指向一个白色对象,而这个新的白色对象会因为指向它的对象已经为黑色而导致这次扫描不会被标记,如果这个新的白色对象没有被其他灰色对象引用或者引用链路上没有灰色对象,这种情况下会出现对象丢失现象
对象丢失解决方案:强弱三色不变式
强三色不变式:强制性不允许黑色对象引用白色对象
弱三色不变式:黑色对象可以引用白色对象,但是需要白色对象正在被其他的灰色对象引用或可达它的链路上存在灰色对象
最终根据强弱三色不变式得出了一种解决方案:barrier屏障(由于需要保证栈的速度,不用在栈上)(一种额外的判断机制 )(类似于生长周期钩子函数)
插入写屏障(对象被引用时触发的机制)
具体操作为:在a对象引用b对象时,b对象被标记为灰色(满足强三色不变式)(也可以理解为新引用的对象会为灰色)
不足:栈需要在结束时短暂使用STW重新扫描栈,大约需要10-100ms(原因:栈中可能会引用堆中对象,所以第一次扫描需要扫描栈,但是却不在栈上使用插入屏障,会有新引用的栈对象被误杀)
删除写屏障(对象被删除时(删除:将引用的对象换为nil)出发的机制)
具体操作为:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色(满足弱三色不变式)(可以理解为被用于的对象被解除引用时,只要不是黑色(后续的节点都处理成灰色或没有后续节点),就会被当为灰色)(也就是说:被删除引用的对象如果后续对象没有加入灰色的待处理队列或者没有确定有没有后续对象,就将当前被删除引用的对象放入待处理,以便遍历后续对象)(总结为:被删除的对象没有确定有没有后续队列,所以需要变成灰色放入待处理队列遍历后续节点,保证后续队列会被标记)
不足:回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清除掉
当进行新的内存分配申请时,GC 会通过 write barrier 技术跟踪调用方和被调用方之间的关系,即写操作会触发 write barrier,然后 MarkSweep 标记器会根据 write barrier 的信息,更新灰色节点的状态。
通过三色标记法实现增量式 GC,可以最大程度地减少程序阻塞时间,并降低因垃圾回收而带来 CPU 占用过高、性能下降等副作用。尽管该算法在运行的效率上可能比一些传统的标记 - 清除算法低一些,但是它有效地平衡了系统实时响应能力和垃圾回收负担。
Go V1.8 三色标记法+混合写屏障机制
具体操作
- GC开始将栈上对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
- GC期间,任何在栈上创建的新对象均为黑色
- 被删除的对象标记为灰色(删除写屏障的特点)
- 被添加的对象标记为灰色(插入写屏障的特点)
满足:变形的弱三色不变式(结合了插入写屏障和删除写屏障的优点)