GC 垃圾回收 #
分配在栈上的数据,随着函数调用栈的销毁便释放了自身占用的内存,可以被程序重复利用。
协程栈也是从堆上分配的,也在 mheap 管理的 span 中,mspan.spanState 会记录该 span 是用作堆内存还是栈内存。
而分配在堆上的数据,他们占用的内存需要程序主动释放才可以重新使用,否则称为垃圾。
三色标记原理 #
三色标记法,白色,灰色和黑色
- 垃圾回收开始会把所有数据(栈、堆、数据段)都标记为白色
- 然后把直接追踪(扫描全局数据区和栈区)到的 root 节点标记为灰色,灰色代表基于当前节点展开的追踪还未完成。
- 基于某个节点的追踪任务完成后标记为黑色,标识有用并且无需基于它再追踪。
- 没有灰色节点后意味着标记工作结束。此时有用的数据为黑色,垃圾都是白色,在清除阶段回收这些白色的垃圾即可。
混合写屏障 #
通过 混合写屏障
防止GC过程中并发修改对象的问题。
混合写屏障
继承了插入写屏障的优点,起始时无需 STW 打快照,直接并发扫描垃圾即可混合写屏障
继承了删除写屏障的优点,赋值器是黑色赋值器,GC期间,任何在栈上创建的新对象,均为黑色。扫描过后就不需要扫描了,这样就消除了插入写屏障最后 STW 的重新扫描栈了。混合写屏障
扫描栈虽然不用 STW,但是扫描某一个具体的栈的时候,还是要停止这个 goroutine 赋值器的工作(针对一个 goroutine 来说,是暂停扫的,要么全灰,要么全黑,是原子状态切换的)
GC 触发时机 #
- 主动触发:调用
runtime.GC
- 被动触发:使用系统监控
sysmon
,该触发条件由runtime.forcegcperiod
控制,默认为 2 分钟。当超过时间没有产生任何 GC 时,强制触发 GC。使用步调算法。。。
GC 流程 #
标记设置 Mark Setup (STW
)
#
打开写入屏障
需要STW, 所有 goroutine 需要暂停工作并执行, 没有抢占之前这里如果存在 tight loop operation
会消耗很长等待的时间.
并发标记 Marking Concurrent #
一旦写入屏障打开, 并发标记就开始工作, 标记阶段主要是标记出还在使用的堆内存.
回收器(collector
) 会占用 25%
的可用 CPU 容量, 使用 Goroutine 执行回收工作并且使用和应用 Goroutine 相同的 P 和 M.
标记流程为:
TODO 补充三色标记流程
- 寻找根节点, 把
全局数据区
和栈区
的节点标记为灰色
标记辅助(Mark Assist)可以用来加速标记阶段执行. 通过 runtime.gcAssistAlloc
函数实现, 当某个goroutine分配内存过快时,调度器会强制其执行更多标记工作
标记终止 Mark Termination (STW
)
#
关闭写入屏障, 执行各种清理任务并计算下一个回收目标.
应用程序恢复到全功率.
并发清除 Sweeping Concurrent #
清除是指回收 reclaim
与堆内存中未被标记为在使用的值关联的内存.
注意这里的 reclaim 不一定是被操作系统立即回收的, 表现形式就是回收了, 但是进程占用的内存没少.
TODO: 补充 reclaim 的操作系统相关接口调用
GC 对应用性能影响 #
- 窃取 25% CPU 容量
- STW 延时