一、内存垃圾的是怎样产生的?
- 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。
- 对于某些早期的编程语言栈上的内存由编译器管理回收,堆上的内存空间需要程序员负责申请与释放。
- Go中的栈上内存仍由编译器负责管理回收,而堆上的内存由编译器和垃圾收集器负责管理回收。
- 垃圾是指程序向堆栈申请的内存空间,随着程序的运行已经不再使用这些内存空间,这时如果不释放他们就会造成垃圾也就是内存泄漏。
下面我们举一个栗子,程序是怎样产生垃圾的
package main
// 假设每个人都有自己的手机
type Person struct {
phone *Phone
}
type Phone struct {
money int
}
func main() {
// 我们定义一个Person为芋圆
yuyuan := new(Person)
// 芋圆刚开始喜欢 iphone13,用的就是 iphone13
iphone := &Phone{money:6999}
yuyuan.phone = iphone
// 芋圆后面又喜欢上了华为手机,于是又立刻换成了华为mate系列
huawei := &Phone{money:5999}
yuyuan.phone = huawei
}
随着芋圆将手机从iPhone换成了华为,芋圆的手机先前只想的iphone内存空间就成了垃圾,这时候就需要对phone只想的内存空间进行回收,否则就会造成内存泄漏。
二、Golang的垃圾回收机制
2.1、Go垃圾回收发展史
runtime.GCdebug.SetGCPercentdebug.FreeOSMemorydebug.SetGCPercent
2.2、常见的垃圾回收算法
- 引用计数:每个对象维护一个引用计数,当被引用对象被创建或被赋值给其他对象时引用计数自动 +1。如果这个对象被销毁,那么计数-1,当计数为0时,回收该对象。
- 优点:对象可以很快被回收,不会出现内存耗尽或者达到阈值才回收。
- 缺点:不能很好的处理循环引用。
- 标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记“被引用”,没有标记的则进行回收。
- 优点:解决了引用计数的缺点。
- 缺点:需要 STW(stop the world),暂时停止程序运行。
- 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率。
- 优点:回收性能好
- 缺点:算法复杂
2.3、go1.3使用的是标记清除法
- 进行STW(stop the worl即暂停程序业务逻辑),然后从main函数开始找到不可达的内存占用和可达的内存占用
- 开始标记,程序找出可达内存占用并做标记
- 标记结束清除未标记的内存占用
- 结束STW停止暂停,让程序继续运行,循环该过程直到main生命周期结束
2.3、三色标记法(go1.5垃圾回收原理)
-
为什么需要三色标记?
三色标记的目的,主要是利用Tracing GC做增量式垃圾回收,降低最大暂停时间。原生Tracing GC只有黑色和白色,没有中间的状态,这就要求GC扫描过程必须一次性完成,得到最后的黑色和白色对象。在前面增量式GC中介绍到了,这种方式会存在较大的暂停时间。
三色标记增加了中间状态灰色,增量式GC运行过程中,应用线程的运行可能改变了对象引用树,只要让黑色对象直接引用白色对象,GC就可以增量式的运行,减少停顿时间。
-
什么是三色标记?
- 黑色 Black:表示对象是可达的,即使用中的对象,黑色是已经被扫描的对象。
- 灰色 Gary:表示被黑色对象直接引用的对象,但还没对它进行扫描。
- 白色 White:白色是对象的初始颜色,如果扫描完成后,对象依然还是白色的,说明此对象是垃圾对象。
三色标记规则:黑色不能指向白色对象。即黑色可以指向灰色,灰色可以指向白色。
-
三色标记的主要流程:
- 初始所有对象被标记为白色。
- 寻找所有Root对象,比如被线程直接引用的对象,把Root对象标记为灰色。
- 把灰色对象标记为黑色,并它们引用的对象标记为灰色。
- 持续遍历每一个灰色对象,直到没有灰色对象。
- 剩余白色对象为垃圾对象。
这种方法看似很好,但是将GC和程序会放一起执行,会因为cpu的调度出现下面这种情况,导致被引用的对象3会被垃圾回收掉,从而出现错误。
分析分析上述存在Bug的根源,主要有以下两种情况
- 一个白色对象被黑色对象引用
- 灰色对象与它之间的可达关系的白色对象遭到破坏
强三色不变式和弱三色不变式
- 强三色不变式:不允许黑色对象引用白色对象
- 弱三色不变式:黑色对象可以引用白色,白色对象存在其他灰色对象对他的引用,或者他的链路上存在灰色对象
为了实现这俩种不变式的设计思想,从而引出了屏障机制,即在程序的执行过程中加一个判断机制,满足判断机制则执行回调函数。
A.Next = B
A.Next = &C{}
写屏障可以解决这个问题,当对象引用树发生改变时,即对象指向关系发生变化时,将被指向的对 象标记为灰色,维护了三色标记的约束:黑色对象不能直接引用白色对象,这避免了使用中的对象被释放。
A.Next = &C{}
2.4、Go1.8三色标记 + 混合写屏障
基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,所带来的性能瓶颈,Go在1.8引入了混合写屏障的方式实现了弱三色不变式的设计方式,混合写屏障分下面四步
- GC开始时将栈上可达对象全部标记为黑色(不需要二次扫描,无需STW)
- GC期间,任何栈上创建的新对象均为黑色
- 被删除引用的对象标记为灰色
- 被添加引用的对象标记为灰色
下面为混合写屏障过程:
runtime.GC()runtime.forcegcperiod
GOGC
GOGC
该参数取值范围为0~100,默认值是100,单位是百分比。
GOGC = 75
5 * (1 + 75%) = 8.75MB
等heap占用内存大小达到8.75MB会触发1轮GC。
GOGC
总结
本文主要介绍了内存的垃圾是怎样回收的,Go的垃圾回收机制,着重讲解了三色标记法和写屏障的过程。