🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## **gc 是怎么实现的?** ### **GC机制随着golang版本变化如何变化的** GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。 GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通 GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。 ### **版本流程** #### **GoV1.3** 1.3之前: * STW 暂停 * 执行标记 * 执行数据回收 * 停止 STW 1.3优化: * STW 暂停 * 执行标记 * 停止 STW * 执行数据回收 #### **GoV1.5** 1. (写屏障)所有的对象放到白色集合, 2. 遍历一次根节点,得到灰色节点, 3. 遍历灰色节点,将可达的对象,从白色标记灰色,遍历之后的灰色标记成黑色, 4. 由于并发特性,此刻外界向在堆中的对象发生添加对象,以及在栈中的对象添加对象,在堆中的对象会触发插入屏障机制,栈中的对象不触发, 5. 由于堆中对象插入屏障,则会把堆中黑色对象添加的白色对象改成灰色,栈中的黑色对象添加的白色对象依然是白色, 6. 循环第 5 步,直到没有灰色节点, 7. 在准备回收白色前,重新遍历扫描一次栈空间,加上 STW 暂停保护栈,防止外界干扰(有新的白色会被添加成黑色)在 STW 中,将栈中的对象一次三色标记,直到没有灰色, 8. 停止 STW,清除白色。至于删除写屏障,则是遍历灰色节点的时候出现可达的节点被删除,这个时候触发删除写屏障,这个可达的被删除的节点也是灰色,等循环三色标记之后,直到没有灰色节点,然后清理白色,删除写屏障会造成一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮 GC 中被清理掉。 #### **GoV1.8** 1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW), 2、GC期间,任何在栈上创建的新对象,均为黑色。 3、被删除的对象标记为灰色。 4、被添加的对象标记为灰色。 ### **插入写屏障、删除写屏障,混合写屏障** **屏障的作用:避免程序运行过程中,变量被误回收;减少STW的时间** #### **插入写屏障** 当一个对象引用另外一个对象时,将另外一个对象标记为灰色,以此满足强三色不变性,不会存在黑色对象引用白色对象。 #### **删除写屏障** 在灰色对象删除对白色对象的引用时,将白色对象置为灰色,其实就是快照保存旧的引用关系,这叫STAB(snapshot-at-the-beginning),以此满足弱三色不变性。 #### **混合写屏障** 保证弱三色不变性;该写屏障会将覆盖的对象标记成灰色(删除写屏障)并在当前栈没有扫描时将新对象也标记成灰色(插入写屏障):写屏障会将被覆盖的指针和新指针都标记成灰色,而所有新建的对象都会被直接标记成黑色。 ## **Go 语言中 GC 的流程是什么?** Go1.14 版本以 STW 为界限,可以将 GC 划分为五个阶段: 1. GCMark 标记准备阶段,为并发标记做准备工作,启动写屏障 2. STWGCMark 扫描标记阶段,与赋值器并发执行,写屏障开启并发 3. GCMarkTermination 标记终止阶段,保证一个周期内标记任务完成,停止写屏 障 4. GCoff 内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭 5. GCoff 内存归还阶段,将过多的内存归还给操作系统,写屏障关闭。 ## **GC 如何调优** 通过 go tool pprof 和 go tool trace 等工具 1. 控制内存分配的速度,限制 Goroutine 的数量,从而提高赋值器对 CPU 的利用率。 2. 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例 如提前分配足够的内存来降低多余的拷贝。 3. 需要时,增大 GOGC 的值,降低 GC 的运行频率。 ## **GC 触发时机** 1. 主动触发:调用 runtime.GC 2. 被动触发: * 使用系统监控,该触发条件由 runtime.forcegcperiod 变量控制,默认为 2 分 钟。当超过两分钟没有产生任何 GC时,强制触发 GC。 * 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。如 Go 的 GC 是一种比例 GC, 下一次 GC 结束时的堆大小和上一次 GC 存活堆大小成比例. ## **GC 中 stw 时机,各个阶段是如何解决的?** 1)在开始新的一轮 GC 周期前,需要调用 gcWaitOnMark 方法上一轮 GC 的标记结束(含扫描终止、标记、或标记终止等)。 2)开始新的一轮 GC 周期,调用 gcStart 方法触发 GC 行为,开始扫描标记阶段。 3)需要调用 gcWaitOnMark 方法等待,直到当前 GC 周期的扫描、标记、标记终止完成。 4)需要调用 sweepone 方法,扫描未扫除的堆跨度,并持续扫除,保证清理完成。在等待扫除完毕前的阻塞时间,会调用 Gosched 让出。 5)在本轮 GC 已经基本完成后,会调用 mProf\_PostSweep 方法。以此记录最后一次标记终止时的堆配置文件快照。 6)结束,释放 M。