当前位置: 首页 > news >正文

GO语言中的垃圾回收(GC)

Go语言的GC机制在不同版本间的演进

一、Go 1.3及之前:标记-清除算法(Mark-Sweep)

  • 核心特点
    • 全局STW:整个GC周期需要暂停所有应用线程
    • 简单实现:标记阶段遍历所有可达对象,清除阶段回收未标记对象
    • 性能问题:STW时间随堆大小增长(数百毫秒到几秒)
    • 示例场景:对于GB级堆内存,一次GC可能导致服务完全停顿1秒以上

二、Go 1.3 ~ Go 1.5:标记-清除算法 + 并发清除优化

  • 关键改进
    • 并发清除:清除阶段与应用程序并发执行(但标记阶段仍需STW)
    • 内存分配器优化:引入mcache/mcentral架构提升分配效率
    • STW时间:标记阶段仍占主导,STW约100ms(取决于堆大小)
    • 示例数据:对1GB堆内存,Go 1.3的STW约200ms,Go 1.4优化到约150ms

三、Go 1.5 ~ Go 1.8:并发标记/清除 + Dijkstra写屏障

  • 革命性改进
    • 并发标记:标记阶段与应用程序并发执行
    • Dijkstra插入写屏障:黑色对象指向新对象时,新对象被标记为灰色
      // Dijkstra写屏障伪代码
      writePointer(slot, ptr):if isBlack(*slot):shade(ptr)      // 新对象标记为灰色*slot = ptr
      
    • STW时间:降至10ms以内(初始STW+最终STW)
    • 局限性:需要在标记结束后重新扫描栈(导致最终STW较长)

四、Go 1.8 ~ 至今:并发标记/清除 + 混合写屏障(Hybrid Write Barrier)

  • 关键改进
    • 混合写屏障:结合Dijkstra插入屏障和Yuasa删除屏障的优点
      // 混合写屏障伪代码
      writePointer(slot, ptr):shade(*slot)        // 旧引用标记为灰色(删除屏障)if current stack is grey:shade(ptr)      // 新引用标记为灰色(插入屏障)*slot = ptr
      
    • 栈扫描优化
      • 栈在标记开始时被标记为灰色
      • 栈上的写操作触发写屏障
      • 标记期间无需重新扫描栈(仅需在初始STW时扫描一次)
    • STW时间:亚毫秒级(通常<1ms)
    • 典型数据:对于5GB堆内存,Go 1.8的STW约0.5ms,而Go 1.5约8ms

五、Go 1.18+:增量GC + Pacer机制

  • 进一步优化
    • 增量GC:将长时间的标记阶段拆分为多个小阶段,减少单次STW压力
    • Pacer机制:基于反馈控制理论,动态调整GC触发时机和并发度
    • 内存管理:更激进地归还未使用内存给操作系统

六、版本演进总结表

版本范围核心算法写屏障类型STW时间关键改进点
Go 1.3-标记-清除(全STW)数百ms-秒级简单直接实现
Go 1.4标记-清除 + 并发清除~100ms并发清除优化
Go 1.5-1.7并发标记/清除Dijkstra插入屏障~10ms并发标记、栈重新扫描
Go 1.8-并发标记/清除混合写屏障<1ms栈一次扫描、增量GC
Go 1.18+并发标记/清除 + Pacer混合写屏障<0.5ms自适应GC控制、内存回收优化

GC从开始前到结束后的整个过程

Go 1.18+ 的垃圾回收(GC)在继承之前版本并发标记/清除和混合写屏障的基础上,引入了增量 GCPacer 机制,进一步优化了 GC 开销和堆内存管理。以下从触发条件到结束后的完整流程进行详细描述:

一、GC 触发前的准备阶段

1. 触发条件

GC 的触发由以下因素共同决定:

  • 堆内存增长阈值(核心触发条件):
    • 当堆内存使用量达到上次 GC 结束时的 GOGC 百分比(默认 100%)
    • 例如:上次 GC 后堆大小为 100MB,则堆增长到 200MB 时触发
  • 定时触发
    • 默认每 2 分钟强制触发一次(可通过 GODEBUG=gctrace=1 观察)
  • 手动触发
    • 调用 runtime.GC() 或通过 pprof 等工具触发
2. Pacer 机制
  • 自适应控制:基于历史 GC 数据预测下一次触发时机,平衡延迟与吞吐量
  • 关键指标
    • heap_live:当前存活对象大小
    • heap_goal:目标堆大小(决定下一次触发阈值)
    • gc_cpu_fraction:GC 允许占用的 CPU 比例(默认 25%)

二、GC 启动阶段(初始 STW)

当满足触发条件时,GC 进入初始 STW(Stop The World)阶段:

1. 暂停所有 P(处理器)
  • 抢占机制:通过 sync/atomic 原子操作通知所有 Goroutine 暂停
  • 协作式抢占:运行中的 Goroutine 在安全点(如函数调用、循环边界)主动暂停
2. 标记根对象
  • 根集合包括:
    • 全局变量
    • 所有活跃 Goroutine 的栈
    • 寄存器中的指针
  • 栈处理
    • 扫描所有栈,标记为灰色(混合写屏障特性)
    • 启用混合写屏障,处理后续指针更新
3. 初始化增量 GC
  • 将标记工作拆分为多个小任务(标记单元)
  • 计算每个标记单元的工作量和时间预算

三、并发标记阶段(增量 GC)

初始 STW 结束后,GC 与应用程序并发运行,采用增量方式执行标记:

1. 标记任务调度
  • 工作窃取算法:多个 GC 工作线程(Goroutine)并行处理标记队列
  • 增量执行:每个标记任务执行时间受限制(默认 <10ms),避免长时间占用 CPU
2. 三色标记过程
  • 灰色对象处理
    • 从标记队列取出灰色对象
    • 遍历其所有指针字段,将指向的白色对象标记为灰色并加入队列
    • 将当前对象标记为黑色
  • 混合写屏障
    // 混合写屏障伪代码
    writePointer(slot, ptr):shade(*slot)        // 旧引用标记为灰色(删除屏障)if current stack is grey:shade(ptr)      // 新引用标记为灰色(插入屏障)*slot = ptr
    
3. 并发标记终止检查
  • 标记终止条件
    • 标记队列为空
    • 所有写屏障记录的指针更新已处理完毕
  • 终止检查过程
    • 尝试进入最终 STW 阶段
    • 若有新的指针更新被写屏障捕获,则回退并继续标记

四、最终 STW 阶段

完成并发标记后,进入短暂的最终 STW 阶段:

1. 处理剩余标记工作
  • 扫描所有栈(仅确认,无需重新扫描,因混合写屏障已处理)
  • 处理写屏障日志中的剩余记录
  • 确保没有漏标的可达对象
2. 标记终止(Mark Termination)
  • 计算本次 GC 的统计数据(堆大小、GC 耗时等)
  • 更新 Pacer 状态,调整下一次触发阈值

五、并发清除阶段

最终 STW 结束后,GC 进入并发清除阶段:

1. 清除白色对象
  • 遍历所有 mspan(内存管理单元),回收未标记的白色对象
  • 清除操作与应用程序并发进行,不影响新对象分配
2. 内存归还操作系统
  • 长时间未使用的大块内存(通过 madvise 系统调用)归还操作系统
  • 激进内存回收:Go 1.18+ 优化了内存归还策略,减少内存占用
3. 增量清除
  • 将清除工作拆分为小任务,避免单次清除操作耗时过长

六、GC 结束后的恢复阶段

1. 更新 GC 统计信息
  • 更新 runtime.MemStats 中的各项指标:
    • HeapAlloc:当前堆使用量
    • HeapSys:从系统获取的内存总量
    • NumGC:GC 次数
    • PauseTotalNs:GC 暂停总时间
2. 调整 Pacer 参数
  • 根据本次 GC 数据调整下一次触发阈值:
    // 简化的 Pacer 计算公式
    next_heap_goal = heap_live * (1 + GOGC/100) * (1 + gc_overhead)
    
    • gc_overhead:基于本次 GC 耗时动态调整的系数
3. 重置写屏障
  • 禁用混合写屏障,恢复正常指针写操作

七、完整流程时间线

┌───────────────────────────────────────────────────────────────────────┐
│                              应用运行阶段                               │
└───────────────────────────┬─────────────────────────────────────────────┘│ 堆内存增长达到阈值/定时触发/手动触发▼
┌───────────────────────────────────────────────────────────────────────┐
│                          初始 STW 阶段 (短暂暂停)                        │
│ 1. 暂停所有 P                                                           │
│ 2. 标记根对象 (全局变量、栈、寄存器)                                    │
│ 3. 初始化增量 GC 任务                                                   │
└───────────────────────────┬─────────────────────────────────────────────┘│▼
┌───────────────────────────────────────────────────────────────────────┐
│                          并发标记阶段 (增量执行)                         │
│ 1. 启动多个 GC 工作线程                                                 │
│ 2. 增量处理灰色对象队列,标记可达对象                                   │
│ 3. 启用混合写屏障,处理指针更新                                          │
│ 4. 定期检查标记终止条件                                                 │
└───────────────────────────┬─────────────────────────────────────────────┘│ 标记终止检查 (尝试进入最终 STW)▼
┌───────────────────────────────────────────────────────────────────────┐
│                          最终 STW 阶段 (短暂暂停)                        │
│ 1. 确认所有标记工作完成                                                 │
│ 2. 标记终止,计算统计数据                                                │
└───────────────────────────┬─────────────────────────────────────────────┘│▼
┌───────────────────────────────────────────────────────────────────────┐
│                          并发清除阶段 (增量执行)                         │
│ 1. 增量回收白色对象内存                                                 │
│ 2. 归还长时间未使用内存给操作系统                                          │
└───────────────────────────┬─────────────────────────────────────────────┘│▼
┌───────────────────────────────────────────────────────────────────────┐
│                            GC 结束恢复阶段                               │
│ 1. 更新 GC 统计信息                                                       │
│ 2. 调整下次 GC 触发参数                                                   │
│ 3. 重置写屏障                                                           │
└───────────────────────────────────────────────────────────────────────┘

八、性能影响与优化点

  1. STW 时间:初始 STW 和最终 STW 均控制在亚毫秒级(通常 <0.5ms)
  2. 并发标记/清除:消耗 CPU 资源(默认控制在 25% 以内)
  3. 增量 GC:减少单次 GC 操作对应用的影响,适用于低延迟场景
  4. 内存管理:更激进地归还未使用内存,降低驻留内存

九、监控 GC 过程的工具

  1. GC 日志:通过 GODEBUG=gctrace=1 查看详细 GC 信息
  2. pprof:分析 GC 耗时分布
  3. expvar:监控实时内存指标
  4. trace:可视化 GC 与应用的并发执行过程
http://www.dtcms.com/a/278481.html

相关文章:

  • 怎么挑选最新贝琪入门电钢琴才高效?
  • Java进程、线程与协程对比
  • GD32/STM32嵌入CMSIS-DSP的库(基于Keil)
  • 2025年 GitHub 主流开源视频生成模型介绍
  • Go语言第一个程序--hello world!
  • arthas:Java 应用问题诊断利器
  • 企业培训笔记:axios 发送 ajax 请求
  • vue中计算属性的介绍
  • 前端基础知识TypeScript 系列 - 08(TypeScript 装饰器的理解)
  • 代理模式详解:代理、策略与模板方法模式
  • SpringMVC1
  • GraphRAG核心提示词工程完整中文版
  • VyOS起步指南:用Docker快速搭建网络实验环境
  • 分享三个python爬虫案例
  • HTML应用指南:利用GET请求获取河南省胖东来超市门店位置信息
  • STM32新建工程
  • HTB 赛季8靶场 - Outbound
  • 微算法科技技术创新,将量子图像LSQb算法与量子加密技术相结合,构建更加安全的量子信息隐藏和传输系统
  • 复习笔记 38
  • 安卓基于 FirebaseAuth 实现 google 登录
  • 【小米训练营】C++方向 实践项目 Android Player
  • C++ 左值右值、左值引用右值引用、integral_constant、integral_constant的元模板使用案例
  • 量子计算新突破!阿里“太章3.0”实现512量子比特模拟(2025中国量子算力巅峰)
  • ethers.js-5–和solidity的关系
  • RPC 框架学习笔记
  • Spark 之 like 表达式
  • 软件测试中的BUG等级与生命周期详解
  • 走近科学IT版:EasyTire设置了ip,但是一闪之后就变回到原来的dhcp获得的地址
  • ros2版本自定义插件的实现与热插拔
  • 设计模式(行为型)-迭代器模式