golang 不需要开发者关心如何GC,不需要像C或者C++那样精准的控制每一段内存,防止内存泄漏,golang 有auto release 机制,它是如何做到的呢。
golang GC 演化历程
- STW
- Martk STW ,sweep 并行
- 三色标记法
- 新加入hybrid write barrier(混合写屏障,避免re-scan)提升三色标记法
Mark STW & Sweep(标记清除法)
- 步骤:
- 找出不可达的对象,标记
- 清除标记好的对象
- 重复上述操作,直到process结束
- 问题:
- STW会使程序出现停顿
- 标记会扫描整个heap
- 清除数据会产生heap碎片
三色标记法
三色标记法方案,支持并行GC,即用户代码可以和GC代码同时运行。具体来讲,Golang GC分为几个阶段:
- Mark阶段该阶段又分为两个部分:
- Mark Prepare:初始化GC任务,包括开启写屏障(write barrier)和辅助GC(mutator assist),统计root对象的任务数量等,这个过程需要STW。
- GC Drains: 扫描所有root对象,包括全局指针和goroutine(G)栈上的指针(扫描对应G栈时需停止该G),将其加入标记队列(灰色队列),并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行。
- Mark Termination阶段:该阶段主要是完成标记工作,重新扫描(re-scan)全局指针和栈。因为Mark和用户程序是并行的,所以在Mark过程中可能会有新的对象分配和指针赋值,这个时候就需要通过写屏障(write barrier)记录下来,re-scan 再检查一下,这个过程也是会STW的。
- Sweep: 按照标记结果回收所有的白色对象,该过程后台并行执行。
- Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC。
总结一下,Golang的GC过程有两次STW:第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).
GC触发时间
- 频繁申请和释放内存不一定会触发GC
- 触发时机:
- 当前堆上活跃的对象大于初始化时设置的阈值
- 达到GC定时,2m
我们可以从代码上优化GC导致的STW
- 减少对象分配
- 使用sync.Pool
- 对GC友好
- 有效分担对象存储压力