golang系列之-垃圾回收
go的垃圾回收原理官方说法是是三色标记法+混合写屏障,但是,理论上怎么说是一回事,具体实现又是另一回事了,或者说实现已经偏离文档描述。总的来说,GC这部份的内容要比GMP跟内存分配都要复杂的多且出人意料。当前go版本:1.24
前言
目标对象
Go的GC并不扫描整个内存,只关注以下区域:
- heap上分配的对象(在mspan管理范围)
- data/bss段的全局变量
- 栈上的引用(扫描stackRoots)
触发类型
触发类型 | 备注 |
---|---|
gcTriggerHeap | 堆大小触发,heap内存达到一个临界点触发(最常用) |
gcTriggerTime | 时间触发,超时2min未执行GC则强制执行 |
gcTriggerCycle | 手动触发,用户调用runtime.GC() |
角色/分工
角色 | 作用 | 工作阶段 | 备注 |
---|---|---|---|
Mark Worker(标记工作线程) | 遍历对象图,标记存活对象,防止被错误回收 | 标记阶段(Marking) | 运行时创建多个并发 Mark Worker,加快标记速度 |
Sweeper(清扫器) | 清理未标记的对象,将其内存释放回空闲列表(mheap.free) | 清扫阶段(Sweeping) | 逐步清理,避免一次性 STW(Stop The World) |
Scavenger(内存回收器) | 释放长期未使用的堆内存,归还给OS以减少RSS | 后台运行(定期触发) | 主要针对大对象或空闲mspans,减少物理内存占用 |
标记工作线程
其中,标记工作线程的会根据工作模式进一步区分,如下
标记工作线程模式 | 备注 |
---|---|
gcMarkWorkerNotWorker | 默认,未运行 |
gcMarkWorkerDedicatedMode | 专用标记任务,最高优先级,非抢占 |
gcMarkWorkerFractionalMode | 比例标记任务,跟其他g共享时间,可被抢占 |
gcMarkWorkerIdleMode | 空闲时执行的低优先级标记任务,需要p空闲 |
CPU限制
标记工作线程的CPU使用率被限制在25%(GOGC=100时),假设系统使用一个6核CPU,那么GC在标记阶段大约会使用1.5个CPU资源:
- 1个CPU由Dedicated(专用模式)的标记工作线程持续占用,该线程不允许抢占,直到标记任务完成
- 0.5个CPU由Fractional(比例模式)的标记工作线程使用,该线程仅在由额外CPU资源可用时运行,并会根据系统负责动态调整自身的CPU使用率
完整运行流程
Sweep Termination(清理终止)
- STW(Stop The World),确保所有P都达到GC安全点
- 完成上一轮GC未完成的sweep(清扫),回收剩余的的mspan
- 准备GC统计数据,为新一轮的GC计算目标heap大小、触发阈值等
Mark(标记)
- STW,切换GC状态
- gcphase从_GCoff切换到_GCmark
- 开启写屏障(write barrier),允许Mutator协助GC标记,以维护三色标记不变性
- 启动GC后台线程,执行并发标记任务
- 根对象入队(包括栈、全局变量)
- 恢复世界(Start The World),GC线程进入并发标记阶段
- 从根对象开始标记,遍历所有可达对象
- 扫描灰色对象(已发现但未完全扫描的对象)并进行扫描,将其置黑,并将其引用的对象入队为灰色
- 混合写屏障(Hybrid Write Barrier)确保一致性
- 完成标记
- STW,切换GC状态
Mark Termination(标记终止)
- STW,切换GC状态
- gcphase从_GCmark切换到_GCmarktermination
- 停止并发标记任务
- 执行终结器finalizer,如果有的话
- 清理mcache以确保没有悬挂对象
- STW,切换GC状态
Sweep(清理)
- 切换GC状态
- gcphase从_GCmarktermination切换回_GCoff
- 关闭写屏障(Mutator不再协助GC标记)
- 恢复世界(Start The World),进入并发清扫阶段
- 清理未被标记的对象
- 回收mspan到mheap或mcentral,部份回收到mcache
- Mutator分配内存时,可能会触发增量清扫(Incremental Sweeping),加快回收过程
- 切换GC状态
满足触发条件,启动下一轮GC
简单的说,标记然后清扫