拜读过各个大佬Golang垃圾回收机制相关文章。然而理解力有限,至今还挂在懵逼树上啃懵逼果。自己重新整理一下思路。
一、判断对象是否可以回收
首先,在理解GC流程前看看什么样的对象可以被回收。同广泛应用的Java语言一样,Golang采用根搜索发来判断对象是否具备GC条件。扫描过程从根对象开始,跟对象不可达的区域则认为是不再被引用可以回收。以下列出了跟对象的范围:(参考)
1.全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
2.执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。
3.寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。
二、主要流程
开篇一张图 内容全靠编
1.一轮完整的 GC,总是从 Off,如果不是 Off 状态,则代表上一轮GC还未完成,如果这时修改指针的值,是直接修改的。
2.Stack scan 栈扫描阶段,此阶段会做一些准备工作,Stop The World(STW)会暂停当前所有goroutine并且开启写屏障。STW是保证GC实现的正确性而停止内存动态变化的重要步骤,减少STW时间也是Golang的GC优化主要目的。Golang的GC过程是并发标记的过程,此过程中写屏障的主要作用是监控指针数据的变动,当被标记的对象引用其他对象有变化时回收期能够感知。因此写屏障会贯穿这个标记过程。
3.Mark 标记阶段,此阶段就是采用三色标记法对根集合可达的对象进行标记,此过程与用户代码并发执行。
三色标记法可以看成是一个广度优先遍历。我们用队列来解释这个过程,白色对象被标记成灰色对象看成入队,灰色对象标记成黑色对象看成是出队。在最开始阶段所有的对象都是白色的,此时队列为空。根集合看成是树的第一层结构,第一批入队列的为根集合对象。每次从队列头部节点取出对象时将该对象的所有子节点入队列,此时头部节点被标记成黑色,其子节点被标记成灰色。当所标记阶段结束,队列为空,此时所有根集合可达的对象均从队列经过经过了标记灰色->标记黑色的过程。而根集合不可达的对象依然是白色,这些白色对象则是我们GC的目标对象。
4.Mark termination 标记结束阶段,此阶段已不需要再次扫描栈和全局变量,此阶段会在此STM,结束后会关闭写屏障。至此标记阶段完成。
5.Sweep 清扫阶段,根据需要回收未被标记的对象,并且为下一轮GC调整适合的阈值。
6.Off 结束阶段,本轮GC结束
三、GC触发
Golang 的GC触发存在三种方式:
1.出动触发:调用runtime.GC来触发GC,此调用阻塞式地等待当前 GC 运行完毕。
2.被动触发:使用系统监控,当超过两分钟没有产生任何 GC 时,强制触发 GC。
3.被动触发:在分配内存时,会判断当前的Heap内存分配量是否达到了触发一轮GC的阈值(每轮GC完成后在Sweep阶段,该阈值会被动态设置),如果超过阈值,则启动一轮GC。
四、Question
1.Golang垃圾回收不使用分代收集的方式
分代回收按照对象的生存周期长短分开回收,存活时间短的对象更倾向于被回收,以此提高GC效率。但是Golang编译器会将大部分新生对象分配在栈上,通过逃逸分析判断该对象是否被分配到堆上。方法执行完成,分配到栈上的对象随着栈一起被回收,此过程不需要GC接入。而逃逸到堆上的对象才是GC回收的目标。
2.Golang垃圾回收不需要整理
GC中的整理是为了解决内存碎片问题。Golang的内存分配借鉴与tcmalloc,在向内存申请资源的时候并非申请物理上连续的资源而是用指针串起逻辑上连续的内存。在GC结束后依然将回收的对象用指针建立成逻辑上连续的空间。