上篇说到超超从汇编的角度的解析了make和new的区别,下面来到了面试中常见的考点GC,Go的GC常常因为性能问题被业界所诟病,下面跟着超超来看看内存垃圾是如何产生的,以及Go从1.3到1.8在GC上做了哪些改进吧!
一、内存垃圾
面试官:你知道程序的垃圾是怎么产生的吗?
考点:Go内存管理
超超:程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。对于C++等早期编程语言栈上的内存由编译器管理回收,堆上的内存空间需要编程人员负责申请与释放。在Go中栈上内存仍由编译器负责管理回收,而堆上的内存由编译器和垃圾收集器负责管理回收,给编程人员带来了极大的便利性。
垃圾是指程序向堆栈申请的内存空间,随着程序的运行已经不再使用这些内存空间,这时如果不释放他们就会造成垃圾也就是内存泄漏。例如下面这段程序
package main
//假设每个人都拥有自己都一部手机
type Person struct {
phone *Phone
}
type Phone struct {
money int
}
func main() {
//定义一个Person为超超
chao := new(Person)
//超超一开始用的是iphone12
iphone := &Phone{money: 6599}
chao.phone = iphone
//华为推出了鸿蒙,于是超超果断入了一部mate40
huawei := &Phone{money: 5899}
chao.phone = huawei
}
phonephone
二、Go的垃圾回收机制
面试官:那你来给我说说Go语言是如何实现GC的吧!
考点:GC的实现
超超:那我从Go1.3开始说起吧,
Go1.3使用的是标记清除法,分下面四步进行
-
进行STW(stop the worl即暂停程序业务逻辑),然后从main函数开始找到不可达的内存占用和可达的内存占用
-
开始标记,程序找出可达内存占用并做标记
-
标记结束清除未标记的内存占用
-
结束STW停止暂停,让程序继续运行,循环该过程直到main生命周期结束
一开始的做法是将垃圾清理结束时才停止STW,后来优化了方案将清理垃圾放到了STW之后,与程序运行同时进行,这样做减小了STW的时长。但是STW会暂停用户逻辑对程序的性能影响是非常大的,这种粒度的STW对于性能较高的程序还是无法接受,因此Go1.5采用了三色标记法优化了STW。
Go1.5三色标记法
三色标记算法将程序中的对象分成白色、黑色和灰色三类。白色对象表示暂无对象引用的潜在垃圾,其内存可能会被垃圾收集器回收;灰色对象表示活跃的对象,黑色到白色的中间状态,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;黑色对象表示活跃的对象,包括不存在引用外部指针的对象以及从根对象可达的对象。
三色标记法分五步进行
- 将所有对象标记为白色
- 从根节点集合出发,将第一次遍历到的节点标记为灰色放入集合列表中
- 遍历灰色集合,将灰色节点遍历到的白色节点标记为灰色,并把灰色节点标记为黑色
- 循环这个过程
- 直到灰色节点集合为空,回收所有的白色节点
这种方法看似很好,但是将GC和程序会放一起执行,会因为cpu的调度出现下面这种情况,导致被引用的对象3会被垃圾回收掉,从而出现错误。
分析bug的根源所在,主要是因为程序在运行过程中出现了下面俩种情况
- 一个白色对象被黑色对象引用
- 灰色对象与它之间的可达关系的白色对象遭到破坏
因此在此基础上拓展出了俩种方法,强三色不变式和弱三色不变式
- 强三色不变式:不允许黑色对象引用白色对象
- 弱三色不变式:黑色对象可以引用白色,白色对象存在其他灰色对象对他的引用,或者他的链路上存在灰色对象
为了实现这俩种不变式的设计思想,从而引出了屏障机制,即在程序的执行过程中加一个判断机制,满足判断机制则执行回调函数。
屏障机制分为插入屏障和删除屏障,插入屏障实现的是强三色不变式,删除屏障则实现了弱三色不变式。值得注意的是为了保证栈的运行效率,屏障只对堆上的内存对象启用,栈上的内存会在GC结束后启用STW重新扫描。
插入屏障:对象被引用时触发的机制,当白色B对象被黑色A对象引用时,B对象被标记为灰色(栈上对象无插入屏障)。
缺点在于:如果对象1在栈上新创建了一个对象6,由于栈没有屏障机制,所以对象6仍为白色节点会被回收
所以栈在GC迭代结束时(没有灰色节点),会对栈执行STW,重新进行扫描清除白色节点。(STW时间为10-100ms)
删除屏障:对象被删除时触发的机制。如果灰色对象1引用的白色对象1被删除时,那么白色对象1会被标记为灰色。
缺点:这种做法回收精度较低,一个对象即使被删除仍可以活过这一轮再下一轮被回收。(如果对象4没有引用对象3,此时对象3应该作为垃圾被回收,但是对象3却要等到下一轮GC才会被回收)
同样也存在对栈的二次扫描影响程序的效率。
Go1.8 三色标记+混合写屏障
基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,所带来的性能瓶颈,Go在1.8引入了混合写屏障的方式实现了弱三色不变式的设计方式,混合写屏障分下面四步
- GC开始时将栈上可达对象全部标记为黑色(不需要二次扫描,无需STW)
- GC期间,任何栈上创建的新对象均为黑色
- 被删除引用的对象标记为灰色
- 被添加引用的对象标记为灰色
下面为混合写屏障过程
面试官:这个GC什么时候会被触发呢?
考点:GC细节
runtime.forcegcperiod
面试官:说到这那我们再聊一下TCMalloc算法吧。
超超:好的(自动GC用的舒服,面试好难呀😶🌫️
未完待续~
欢迎关注公众号「Golang面试宝典」查看文章最新进展!