什么是GC
垃圾回收就是对程序中不再使用的内存资源进行自动回收的操作。
堆内存上分配的数据对象,不会再使用时,不会自动释放内存,就变成垃圾,在程序的运行过程中,如果不能及时清理,会导致越来越多的内存空间被浪费,导致系统性能下降。
因此需要内存回收,内存回收分为两种方式:
手动释放占用内存空间
可能会出现的问题:
悬挂指针: 释放的早了,后续对数据的访问就会出错,因为对应的内存空间可能已经清空,重新分配,甚至是归还给操作系统了。
内存泄漏: 如果忘了释放,一直占用内存,导致内存泄漏。自动内存回收
程序自动检测对象决定是否要回收其内存。
核心思想:程序中用得到的数据,一定是可以从栈或数据段这些根节点追踪得到的数据,追踪不到的数据,肯定用不到,也就是垃圾。
go gc 是怎么实现的?
一次完整的垃圾回收会分为四个阶段,分别是标记准备、标记开始、标记终止、清理:
- 标记准备(Mark Setup):打开写屏障(Write Barrier),需 STW(stop the world)
- 标记开始(Marking):使用三色标记法并发标记 ,与用户程序并发执行
- 标记终止(Mark Termination):对触发写屏障的对象进行重新扫描标记,关闭写屏障(Write Barrier),需 STW(stop the world)
- 清理(Sweeping):将需要回收的内存归还到堆中,将过多的内存归还给操作系统,与用户程序并发执行
GC机制随着golang版本变化如何变化的?
go 1.3 之前采用标记清除法,需要STW
go 1.5 采用三色标记法,插入写屏障机制(只在堆内存中生效),最后仍需对栈内存进行STW
go 1.8 采用混合写屏障机制**,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率一个概念:STW:stop the word,指程序执行过程中,中断暂停程序逻辑,专门去进行垃圾回收。(STW目的是为了防止GC扫描时内存变化引起的混乱)
标记清除法
从根变量开始遍历所有引用的对象,标记引用的对象,没有被标记的进行回收。
- 开启STW,
- 从根节点出发,标记所有可达对象
- 停止STW,然后回收所有未标记的对象。
- 优点:解决了引用计数的缺点。
- 缺点:需要 STW,暂时停掉程序运行。
三色标记法【改进的标记清除法】+写屏障机制的流程
三色标记法是对标记阶段的改进,原理如下:
- 初始状态所有对象都是白色。
- 从root根出发扫描所有根对象(下图a,b),将他们引用的对象标记为灰色(图中A,B)
那么什么是root呢? 看了很多文章都没解释这这个概念,在这儿说明下:root区域主要是程序运行到当前时刻的栈、寄存器和全局变量。
遍历灰色对象,将灰色对象引用的对象由白色 也变成灰色对象,然后将遍历过的灰色对象变成黑色对象。
循环步骤3,直到灰色对象全部变黑色。重复以上操作
其中通过写屏障(write-barrier)【写屏障就是让goroutine与GC同时运行的手段,大大减少STW的时间,开启后指针传递时会把指针标记,本轮不回收,下轮GC时回收】检测对象有变化;还有一个辅助GC(mutator assist)机制【为防止内存分配过快,GC过程中,辅助GC线程并发运行,协助GC做一部分工作,先做一部分标记工作】,
收集所有白色对象(垃圾)。
插入写屏障
对象被引用时触发的机制(只在堆内存中生效):赋值器这一行为通知给并发执行的回收器,被引用的对象标记为灰色
缺点:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活
删除写屏障
对象被删除时触发的机制(只在堆内存中生效):赋值器将这一行为通知给并发执行的回收器,被删除的对象,如果自身为灰色或者白色,那么标记为灰色
缺点:一个对象的引用被删除后,即使没有其他存活的对象引用它,它仍然会活到下一轮,会产生很大冗余扫描成本,且降低了回收精度
混合写屏障
GC没有混合写屏障前,一直是插入写屏障;混合写屏障是插入写屏障 + 删除写屏障,写屏障只应用在堆上应用,栈上不启用(栈上启用成本很高)
- GC开始将栈上的对象全部扫描并标记为黑色。
- GC期间,任何在栈上创建的新对象,均为黑色。
- 被删除的对象标记为灰色。
- 被添加的对象标记为灰色。
GC 的触发时机
主动触发:
- 调用 runtime.GC() 方法,触发 GC
被动触发:
- 定时触发,该触发条件由
runtime.forcegcperiod
变量控制,默认为 2 分 钟。当超过两分钟没有产生任何 GC 时,触发 GC - 根据内存分配阈值触发,该触发条件由环境变量GOGC控制,默认值为100(100%),当前堆内存占用是上次GC结束后占用内存的2倍时,触发GC