learning_notes

学习笔记

View project on GitHub

内存管理

常见的GC模式

  • 引用计数(销毁引用,计数器-1,为0,回收)
  • 标记清除(mark and sweep) 缺点:每次GC需要暂停所有的运行代码(go1.5之前使用),(go1.5之后使用)变种”三色标记”
  • 分代搜集

内存泄漏

  • 堆内存泄漏(使用内存后未释放)
  • 系统资源泄漏(使用后未关闭句柄)

GC调优

  • 减少对象的分配(对象重用)

go程序内存占用大的问题

  • 是go的垃圾回收有个触发阈值,这次是4M,gc百分比参数是100,下次触发gc是8M
  • 操作系统”拖延症”策略
  • 每次申请1M的内存,然后分割成不同大小的span,给对象使用,不使用的5分钟后交还系统

对齐规则

内存对齐

  • 结构体的成员变量: 编译器默认对齐长度 和 当前成员变量类型的长度(unsafe.Sizeof) 这2者间取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍

  • 结构体本身:编译器默认对齐长度 和 结构体的所有成员变量类型中的最大长度 这2者间 取最大数的最小整数倍作为对齐值

  • 结合以上两点,可得知若编译器默认对齐长度(#pragma pack(n))超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

如何观察GC

  • GODEBUG=gctrace=1
  • go tool trace(有图)
  • runtime.ReadMemStats

Go并发标记清除的,存在什么难点

  • 用户态代码可能并发地更新对象图

什么是根对象

  • 全局变量
  • 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。
  • 寄存器

三色标记原理​​

​- ​白色​​:初始状态,表示对象未被访问,可能为垃圾。 ​- ​灰色​​:对象被标记为可达,但其引用的子对象尚未检查。 ​​- 黑色​​:对象及其所有子对象均被检查,确定为存活对象。

GC 核心流程

1. 标记准备阶段(STW)

  • 暂停所有用户协程(STW),确保一致性。
  • 扫描根对象
    遍历栈、全局变量、寄存器等根对象,直接可达的对象标记为灰色,加入待处理队列。
  • 启用写屏障
    为后续并发标记阶段捕获对象引用变更。

2. 并发标记阶段

  • 恢复用户协程,GC线程与用户程序并发运行。
  • 处理灰色队列
    • 取出灰色对象,遍历其引用的子对象。
    • 若子对象为白色,则将其标记为灰色并加入队列。
    • 当前灰色对象标记为黑色。
  • 写屏障监控
    • 用户程序修改对象引用时,触发混合写屏障(Hybrid Write Barrier)。
    • 若黑色对象引用白色对象,屏障将白色对象标记为灰色(防止漏标)。

3. 标记终止阶段(STW)

  • 再次暂停用户协程,确保所有灰色对象处理完毕。
  • 完成剩余标记工作,确认所有存活对象均为黑色。

4. 并发清除阶段

  • 回收所有白色对象(垃圾)的内存。
  • 用户程序恢复运行。

关键机制

混合写屏障(Hybrid Write Barrier)

  • 插入屏障
    黑色对象引用白色对象时,将白色对象标记为灰色。
  • 删除屏障
    若被删除引用的对象为灰色或白色,则标记为灰色(Go 1.8+优化后主要依赖插入屏障)。
  • 目的
    确保并发标记期间,用户代码修改引用关系不会导致存活对象被误回收。

并发优化

  • GC线程与用户协程交替运行(基于Goroutine调度器)。
  • 利用CPU多核并行处理标记任务,缩短暂停时间。

有了 GC,为什么还会发生内存泄露

  • 预期能被快速释放的内存因被根对象引用而没有得到迅速释放
  • 全局变量或缓存无限增长,对象被长期引用。
  • 未释放的底层资源​
  • goroutine 泄漏
  • 如果一个 goroutine 尝试向一个没有接收方的无缓冲 channel 发送消息,则该 goroutine 会被永久的休眠,整个 goroutine 及其执行栈都得不到释放

触发 GC 的时机是什么?

  • 主动触发,通过调用 runtime.GC 来触发 GC,此调用阻塞式地等待当前 GC 运行完毕。
  • 被动触发,分为两种方式:
    • 使用系统监控,当超过两分钟没有产生任何 GC 时,强制触发 GC。
    • 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。

GC优化

  • 控制内存分配的速度,限制 goroutine 的数量,从而提高赋值器对 CPU 的利用率。
  • 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例如提前分配足够的内存来降低多余的拷贝。
  • 需要时,增大 GOGC 的值,降低 GC 的运行频率。