💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## GC回收的是什么? 在应用程序中会使用到两种内存,分别为堆(Heap)和栈(Stack),GC负责回收堆内存,而不负责回收栈中的内存。 ## 为什么不回收栈内存? 栈是一块专用内存,专门为了函数执行而准备的,存储着函数中的局部变量以及调用栈。除此以外,栈中的数据都有一个特点——简单。比如局部变量不能被函数外访问,所以这块内存用完就可以直接释放。正是因为这个特点,栈中的数据可以通过简单的编译器指令自动清理,并不需要通过 GC 来回收。 ## GC算法种类 * 追踪式垃圾回收算法 * 引用计数法 ## 历程 GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。 GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通 GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。 ### 标记-清除 #### 两个主要的步骤: * 标记(Mark phase) * 清除(Sweep phase) #### 具体步骤 1. 暂停程序业务逻辑, 分类出可达和不可达的对象,然后做上标记。 2. ,开始标记,程序找出它所有可达的对象 3. 开始清除未标记的对象. 4. 停止暂停,继续业务 #### 标记-清除(mark and sweep)的缺点 标记清除算法明了,过程鲜明干脆,但是也有非常严重的问题。 * STW,stop the world;让程序暂停,程序出现卡顿**(重要问题)**; * 标记需要扫描整个heap; * 清除数据会产生heap碎片。 #### V1.3之前 首先启动STW暂停,然后执行标记,再执行数据回收,最后停止STW ![](https://img.kancloud.cn/7f/57/7f57c0bbb35961372a92b4a317896545_2426x578.png) #### V1.3 ![](https://img.kancloud.cn/c1/c8/c1c8c9c72deef24ed7f79f5273155c46_2410x520.png) 将STW的步骤提前了一步,因为在Sweep清除的时候,可以不需要STW停止,因为这些对象已经是不可达对象了,不会出现回收写冲突等问题 ### 三色并发标记法 1. (写屏障)所有的对象放到白色集合, 2. 遍历一次根节点,得到灰色节点, 3. 遍历灰色节点,将可达的对象,从白色标记灰色,遍历之后的灰色标记成黑色, 4. 由于并发特性,此刻外界向在堆中的对象发生添加对象,以及在栈中的对象添加对象,在堆中的对象会触发插入屏障机制,栈中的对象不触发, 5. 由于堆中对象插入屏障,则会把堆中黑色对象添加的白色对象改成灰色,栈中的黑色对象添加的白色对象依然是白色, 6. 循环第 5 步,直到没有灰色节点, 7. 在准备回收白色前,重新遍历扫描一次栈空间,加上 STW 暂停保护栈,防止外界干扰(有新的白色会被添加成黑色)在 STW 中,将栈中的对象一次三色标记,直到没有灰色, 8. 停止 STW,清除白色。至于删除写屏障,则是遍历灰色节点的时候出现可达的节点被删除,这个时候触发删除写屏障,这个可达的被删除的节点也是灰色,等循环三色标记之后,直到没有灰色节点,然后清理白色,删除写屏障会造成一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮 GC 中被清理掉。 >相对于普通的标记清除,减少了STW时间,因为标记过程中不需要STW,是和程序并发执行的 因为标记和清除是并发执行的,所以可能在标记的过程中产生了新的引用,就会导致误清除,所以就加入了写屏障,直接把新的引用标记成灰色,然后在下一轮扫描中再确认是否删除 ### 混合写屏障 1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW), 2、GC期间,任何在栈上创建的新对象,均为黑色。 3、被删除的对象标记为灰色。 4、被添加的对象标记为灰色。 `满足`: 变形的**弱三色不变式**. ### 三色不变性 * 强三色不变性 — 黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象; * 弱三色不变性 — 黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径 ### 插入写屏障 插入到黑色对象中的白色指针,无论其未来是否会被删除,屏障都会对其标记为灰色 满足强三色不变式 ![](https://img.kancloud.cn/4f/f7/4ff715c71ee41d56e25cfec00906f8b2_1080x438.png) ### 删除写屏障 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。 `满足`:**弱三色不变式**. (保护灰色对象到白色对象的路径不会断) (起始快照的屏障,在对象被接触引用后,会立即将原引用对象标记为灰色) ### GC 触发时机 1. 主动触发:调用 runtime.GC 2. 被动触发: * 使用系统监控,该触发条件由 runtime.forcegcperiod 变量控制,默认为 2 分 钟。当超过两分钟没有产生任何 GC时,强制触发 GC。 * 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。如 Go 的 GC 是一种比例 GC, 下一次 GC 结束时的堆大小和上一次 GC 存活堆大小成比例. ### 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 的运行频率。 ### 插入写屏障详解 Go GC 在混合写屏障之前,一直是插入写屏障,由于栈赋值没有 hook 的原 因,栈中没有启用写屏障,所以有 STW。Golang 的解决方法是:只是需要在结 束时启动 STW 来重新扫描栈。这个自然就会导致整个进程的赋值器卡顿 ### 删除写屏障详解 一个对象即使被删除了最后一个指向它的指针也依旧可以 活过这一轮,在下一轮 GC 中才被清理掉。 ### 混合写屏障详解 1. 混合写屏障继承了插入写屏障的优点,起始无需 STW 打快照,直接并发扫 描垃圾即可; 2. 混合写屏障继承了删除写屏障的优点,赋值器是黑色赋值器,GC 期间,任 何在栈上创建的新对象,均为黑色。扫描过一次就不需要扫描了,这样就 消除了插入写屏障时期最后 STW 的重新扫描栈; 3. 混合写屏障扫描精度继承了删除写屏障,比插入写屏障更低,随着带来的 是 GC 过程全程无 STW; 4. 混合写屏障扫描栈虽然没有 STW,但是扫描某一个具体的栈的时候,还是 要停止这个 goroutine 赋值器的工作(针对一个 goroutine 栈来说,是 暂停扫的,要么全灰,要么全黑哈,原子状态切换)。 #### **屏障的作用:避免程序运行过程中,变量被误回收;减少STW的时间**