内存分配

Go 程序在启动时,会向操作系统申请一定区域的内存,分为堆(Heap)和栈(Stack)。

  • 堆(Heap)
    • 一般来讲是人为手动进行管理,手动申请、分配和释放。所涉及的内存大小并不定,一版会存放比较大的对象。分配相对较慢涉及到的指令动作相对较多。
    • 在 Go 语言中,堆内存由程序申请分配,由 GC(Garbage Collection)负责回收
  • 栈(Stack)
    • 由编译器进行管理,自动申请、分配、释放。一般不会太大,我们常见的函数参数、局部变量等等都会放在栈上。
    • 在 Go 语言中,栈内存会随着函数的调用而进行分配,随着函数的调用结束而回收

性能上,栈内存的使用和回收会更快一些,尽管 Golang 的 GC 很高效,但过程中还会在标记准备阶段标记结束阶段进行 STW,因此,会优先使用栈内存进行分配(因为栈内存更高效,不需要 GC,所以会尽可能将内存分配到栈上),在某些特殊场景下可能会发生内存逃逸到堆上

GC(Garbage Collection)

标记清除法

在 Go 1.3 之前的版本使用的是标记清除法(mark and sweep),大致流程如下:

  1. 暂停程序业务逻辑,找出不可达对象和可达对象
  2. 开始标记,将程序找出的所有可达对象做上标记
  3. 开始清除,清除未被标记的对象
  4. 停止程序暂停,让程序继续执行
  5. 重复以上4步,直到进程生命周期结束

缺点

  1. STW(Stop The World) 会让程序暂停,程序会出现卡顿
  2. 标记需要扫描整个堆
  3. 清除数据会产生堆碎片

m-and-s

三色标记法

三色(黑白灰)标记法的核心思想是通过对堆对象的多次遍历,将堆内的所有对象通过指定条件最终区分为两种颜色,最后将其中为白色的所有对象清除掉,三色只出现在过程中。

过程

  1. 初始创建的对象都标记为白色
  2. 每次 GC 开始时,从程序根节点(rootSet)开始遍历所有对象,把遍历到的对象都标记为灰色
  3. 继续遍历灰色对象,将其引用的白色对象变为灰色,然后将自身变为黑色
  4. 重复步骤3,直到没有灰色对象
  5. 清除剩下的白色对象

缺点

如果三色标记法标记过程不进行 STW 保护,当下面两种条件满足时,会出现丢失对象的现象

  • 白色对象被黑色对象引用
  • 灰色对象与该白色对象的引用关系遭到破坏

满足这两种条件时,该白色对象实际是可用但他并不会被标记为黑色,就会在清除阶段被清除掉,为了防止这种情况发生引入了三色不变式

三色不变式

  • 强三色不变式
    • 不允许黑色对象引用白色对象
  • 若三色不变式
    • 允许黑色对象引用白色对象,但该白色对象的上游对象必须有灰色对象对其进行引用

屏障机制

屏障技术

为了实现三色不变式,引入了屏障机制

  • 插入写屏障
    • 触发时机
      • 对象被引用时触发
    • 具体实现
      • 在黑色对象A对对象B进行引用时,将对象B标记为灰色
    • 结果
      • 满足强三色不变式,不会存在黑色对象引用白色对象
    • 缺点
      • 在每次 GC 过程中可能会产生一部分被染黑的垃圾对象,只有在下一次 GC 时菜户被回收
      • 在标记阶段,每次进行指针赋值时都会需要进行写屏障,会增加性能开销。为了避免性能问题,可以选择关闭栈上的指针写操作的写屏障(栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“插入屏障”机制,在栈空间的对象操作中不使用)。当发生栈上写操作时,将栈标记为恒灰,但此举产生了灰色赋值器,需要在标记终止阶段 STW 时对这些栈进行重新扫描。
  • 删除写屏障
    • 触发时机
      • 对象被删除时触发
    • 具体实现
      • 被删除的对象如果自身是灰色或白色,都将其标记为灰色
    • 结果
      • 满足弱三色不变式,保护灰色对象到白色对象的引用不会断
    • 缺点
      • 回收精度不足

混合写屏障

Go 在1.8版本中为了简化 GC 流程,同时减少标记终止阶段的重扫(rescan)成本,将插入屏障和删除屏障进行混合,形成混合写屏障。

混合写屏障的基本思想是,对正在被覆盖的对象进行着色,且如果当前栈未完成扫描,则同样对指针进行着色。

三色标记+混合写屏障 GC 过程
  1. GC 开始时优先扫描栈,将栈标记为黑色,其上可达对象全部标记为黑色,之后不再需要二次扫描,无需 STW
  2. GC 期间,任何栈上创建的新对象均被标记为黑色
  3. 被删除对象标记为灰色
  4. 被添加对象标记为灰色

一次完整的 GC 会分为四个阶段:标记准备,标记,结束标记,清理。在标记准备标记结束阶段仍需短暂的 STW ,标记阶段会降低程序的性能

GC 的标记是广度优先

gcStart 三种触发 GC 的节点

  • runtime.GC
  • runtime.mallocgc
  • forcegchelper

逃逸分析

TODO
TODO
TODO
TODO
TODO
TODO

Reference