什么是GC
GC
堆内存上分配的数据对象,不会再使用时,不会自动释放内存,就变成垃圾,在程序的运行过程中,如果不能及时清理,会导致越来越多的内存空间被浪费,导致系统性能下降。
因此需要内存回收,内存回收分为两种方式
1.手动释放占用的内存空间
可能会出现的问题:
悬挂指针: 释放的早了,后续对数据的访问就会出错,因为对应的内存空间可能已经清空,重新分配,甚至是归还给操作系统了。
内存泄漏: 如果忘了释放,一直占用内存,导致内存泄漏。
2.自动内存回收
开发人员无需手动管理释放已经分配但是没有引用的对象内存,而由程序自动检测对象决定是否要回收其内存。
系统如何检测哪些应该是被回收的数据对象呢?
**核心思想:**程序中用得到的数据,一定是可以从栈或数据段这些根节点追踪得到的数据,追踪不到的数据,肯定用不到,也就是垃圾。
go语言中的GC
go语言GC机制经过多年的迭代最终性能良好。
一个概念:
STW:stop the word,指程序执行过程中,中断暂停程序逻辑,专门去进行垃圾回收。
标记清除法
把根数据段上的数据作为root,基于他们进行进一步的追踪,追踪到的数据就进行标记,最后把没有标记的对象当作垃圾进行释放。
- 开启STW,
- 从根节点出发,标记所有可达对象
- 停止STW,然后回收所有未标记的对象。
三色标记法
白灰黑
- 初始时,所有对象都为白色,
- GC开始,开启SWT,遍历堆栈root,将直接可达的对象标记为灰色,
- 遍历灰色结点,将直接可达的对象标记为灰色,自身标记为黑色,
- 继续执行第三步同样的步骤,直到所有能够访问到的结点都被标记为黑色,
- 关闭SWT,回收所有白色标记的对象。
如果没有SWT,程序正常执行,可能会有如下的情况,导致对象被误当作垃圾回收。
白色对象本来被一个灰色对象引用,但是该灰色对象将该引用赋给了黑色对象,灰对白的引用断开。此时,由于不会对黑色对象的引用进行检测标记,即该白色节点即使被引用也无法被标记为灰色,最终当作垃圾处理掉。
三色标记法出现对象丢失,要满足以下两个条件:
- 条件一:白色对象被黑色对象引用
- 条件二:灰色对象与白色对象之间的可达关系遭到破坏
只要破坏两个中的任何一个不会导致对象丢失的发生。
两种不变式
如何破坏两个条件
- 强不变式: 不允许黑色对象引用白色对象
- 弱不变式: 黑色对象可以引用白色对象,但是白色对象必须直接或间接被灰色对象引用。(保证白色对象一定会被扫描到)
go对上述规则的两种实现机制:
插入写屏障
当一个对象引用另外一个对象时,将另外一个对象标记为灰色。
插入屏障仅会在堆内存中生效,不对栈内存空间生效,这是因为go在并发运行时,大部分的操作都发生在栈上,函数调用会非常频繁。数十万goroutine的栈都进行屏障保护自然会有性能问题。
如果一个栈对象 黑色引用白色对象,白色对象依然会被当作垃圾回收。
因此,最后还需要对栈内存 进行STW,重新rescan,确保所有引用的被引用的栈对象都不会被回收。
删除写屏障
当一个白色对象被另外一个对象时解除引用时,将该被引用对象标记为灰色(白色对象被保护)
缺点:产生内存冗余,如果上述该白色对象没有被别的对象引用,相当于还是垃圾,但是这一轮垃圾回收并没有处理掉他。
混合写屏障法
-
GC刚开始的时候,会将栈上的可达对象全部标记为黑色。
-
GC期间,任何在栈上新创建的对象,均为黑色。
将栈上的可达对象全部标黑,最后无需对栈进行STW,就可以保证栈上的对象不会丢失 -
堆上被删除的对象标记为灰色
-
堆上新添加的对象标记为灰色
总结
go 1.3 之前采用标记清除法,需要STW
go 1.5 采用三色标记法,插入写屏障机制(只在堆内存中生效),最后仍需对栈内存进行STW
go 1.8 采用混合写屏障机制,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率