内存管理
常见的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 的运行频率。