golang面经——GC模块
1.go gc 是怎么实现的?(必问)
分析:
go语言的gc经历了多个版本的迭代和逐步优化,在回答的时候突出三色标记法配合混合写屏障技术就可以了,但是对于问题的追问,其中涉及到的细节要做到心中有数。
回答:
go语言的gc策略是采用三色标记法。但是单纯的三色标记会带来STW,导致执行率不高,所以在1.8版本之后,采用了三色标记法配合混合写屏障技术来实现gc。
Go gc经历了那几个版本?
1.3版本前:普通标记清除法,整个gc过程需要启动 STW,效率极低;
1.5版本:三色标记法,堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要 STW),效率普通;
1.8 版本:三色标记法,混合写屏障机制:栈空间不启动(根节点可达对象和新加入的对象全部标记成黑色),堆空间启用写屏障,整个扫描过程不要 STW,效率高。
STW:指的是在垃圾回收过程中暂停所有用户线程,使整个程序暂停运行,以确保垃圾回收的准确性和一致性。
三色标记法(Tri-color Marking)的解释:
1) 基本思想
三色标记法将堆中的对象分为三种颜色:
- 白色(White):尚未被访问的对象,可能为垃圾。
- 灰色(Gray):已被访问,但其引用的对象尚未全部扫描。处于“待处理”状态。
- 黑色(Black):已被完全扫描,其引用的所有对象都已被标记。确定存活。
根是指:程序中可以直接访问的对象引用起点
三色标记法过程是怎样的?有什么问题?
- 初始化:所有对象为白色。
- 根扫描(Root Scan):从根对象(如全局变量、栈变量)出发,将直接可达的对象标记为灰色,放入待处理队列。
- 并发标记阶段:
- 从灰色对象中取出一个,扫描其引用的所有对象。
- 若引用对象是白色,则将其标记为灰色。
- 当前对象扫描完毕后,标记为黑色。
- 结束条件:当灰色集合为空时,标记完成。
- 清理阶段:所有仍为白色的对象被回收。
如果出现以下的情况,C是他的唯一通路,
这时候我们
A.ref = B // A(黑色)新增对 B 的引用
C.ref = null // C(灰色)删除对 B 的引用
黑色A的指向了一个白色B的,由于A已经是黑色了,不会再进行扫描了,所以白色B就会误判为白色被回收。
这个时候我们就需要插入写屏障还有删除写屏障解决这个问题
插入写屏障:当程序尝试将一个引用从对象 A 添加到对象 B 时,如果 B 是白色(未被标记),则立即对 B 进行标记(变为灰色)。
删除写屏障:当程序尝试删除对象 C 中的一个引用(假设指向 B)时,如果 B 是白色,则立即将 B 标记为灰色。
对象B变为灰色之后可能再次被扫描到。
混合写屏障是怎么工作的?
混合写屏障 = 堆上用插入屏障防止黑指白 + 栈上不加屏障 + GC 结束前 STW 重扫栈,既高效又安全。
栈上不加屏障:当程序修改栈上的指针变量时(如局部变量赋值),GC 不会插入任何写屏障代码。
GC 结束前 STW 重扫栈: 重新扫描所有 goroutine 的调用栈,以确保所有仍被栈引用的对象都被正确标记为“存活”。
2.GC 中 stw 时机,各个阶段是如何解决的?
分析:
go语言的整个gc看流程大致可以分为下面5个步骤
阶段、描述、赋值器状态:
1.清除终止阶段,为下一个阶段的并发标记做准备工作,启动写屏障,STW;
2.扫描标记阶段,与赋值器并发执行,写屏障开启状态,并发;
3.标记终止阶段,保证一个周期内标记任务完成,停止写屏障,STW;
4.内存清除阶段,将需要回收的内存归还到堆中,写屏障关闭状态,并发;
5.内存归还阶段,将过多的内存归还给操作系统,写屏障关闭状态,并发;
回答:
虽然有了混合写屏障技术,go语言的整个gc过程中还是有两次stw,因为写屏障需要开启和关闭,在整个标记过程开始之前需要stw,用于开启写屏障,为标记做准备,在标记终止阶段同样需要短暂的stw来暂定写屏障。
3.GC 的触发时机?
分析:
gc的触发分为手动和被动两种
- 主动触发,通过调用 runtime.GC()来触发 GC,此调用阻塞式地等待当前 GC 运行完毕。
- 被动触发,分为两种方式:
go后台有一系统监控线程,当超过两分钟没有产生任何 GC 时,强制触发 GC。
内存使用增长一定比例时有可能会触发,每次内存分配时检查当前内存分配量是否已达到阈值(环境变量GOGC):默认100%,即当内存扩大一倍时启用GC。我们可以通过debug.SetGCPercent(500)来修改步调,这里表示,如果当前堆大小超过了上次标记的堆大小的500%,就会触发。
而第一次GC的触发的临界值是4MB
回答:
gc可以在代码中通过调用runtime.GC手动触发。
也可以由系统被动触发,当超过两分钟没有gc或者是内存分配达到了一定的阈值的时候就会强制触发gc。
4.GC扫描的根节点有哪些?
分析:
gc的标记是从根节点开始的,扫描的对象是在堆上的,所以要明确堆上的对象是怎么建立关联的,举个例子我们在程序中一般创建对象,假设创建在堆上,然后我们在函数内去操作这个对象,这里我们是通过在函数中的局部变量去操作这个堆上的对象的,所以这种情况下堆上对象一定是和局部变量相关联的,局部变量是保存在栈上的,所以要找到所有堆中可达对象,栈上的对象可以作为根节点全部扫描一遍还有一种情况,对象不是在函数内部创建的,是以全局变量创建的,这种情况下,全局对象是不是也可以作为根节点呢?
所以在回答的时候要思考和联想堆中的对象是怎么关联的,来明确根节点有哪些回答.
根节点包括:
1.全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
2.执行栈上的对象或指针:每个 goroutine 都包含自己的执行栈,这些执行栈上的对象包含栈上的变量及指向分配的堆内存区块的指针。
3.寄存器中的变量:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。