Go
的垃圾回收采用的是 标记-清理(Mark-and-Sweep)算法,就是先标记出需要回收的内存对象块,然后在清理掉;在这里不介绍标记和清理的具体策略,只介绍GC
过程是怎么调度的以及STW
相关
这个算法,会导致STW
(stop the world
)的问题,中断用户逻辑。
触发GC
机制
- 在申请内存的时候,检查当前当前已分配的内存是否大于上次
GC
后的内存的2
倍,若是则触发(主GC
线程为当前M) - 监控线程发现上次
GC
的时间已经超过2
分钟了,则触发;将一个G
任务放到全局G
队列中去。(主GC
线程为执行这个G
任务的M
)
GC
流程
每当触发的时候,在主GC
线程中就会走如下的GC
流程:
stop the world
,等待所有的M
休眠;此时所有的业务逻辑代码都停止- 标记:分配
GC
标记任务,唤醒GCPROC
个M
(就是第一步休眠的那些),分别做这个,直到所有的M
都做完,才结束;并且所有M
再次进入休眠 - 清理:有一个单独的
goroutine
去清理已经标记的内存对象快 start the world
,设置gcwaiting=0
,唤醒所有的M
(不会超过P
个数)
对于上面的三个步骤,分别解释:
stop the world
:
- 设置
gcwaiting=1
,这个在每一个G
任务之前会检查一次这个状态,如是,则会将当前M
休眠; - 如果这个
M
里面正在运行一个长时间的G
任务,咋办呢,难道会等待这个G
任务自己切换吗?这样的话可要等10ms
啊,不能等!坚决不能等!所以会主动发出抢占标记,让当前G
任务中断,再运行下一个G
任务的时候,就会走到第1
步 - 一直等待所有的
M
进入休眠,此时所有的业务逻辑代码都停止 标记: - 根据
GCPROC
的个数,分配成GCPROC
任务段;唤醒GCPROC-1
个M
来执行(当前M
也算一个) - 对于一个
M
,唤醒前设置它的HELPGC
标记,唤醒之后这个M
会立马判断这个标记,如是,则开始做分配给自己的标记任务,如果先做完了,就会从别的M
里面找一些来做 - 等每一个
M
都做完,会再次进入休眠 清理: - 通过设置参数,可以以一个单独
goroutine
运行,这个功能是在1.3
版本之后增加的,这样的话就直接到下一步了,清理过程不是STW
的 - 也可以串行的在主
GC
线程执行;这样的话则清理过程也是STW
的,start the world
: - 设置
gcwaiting=0
- 唤醒
P
个M
来继续做G
任务(此时没有HELPGC
标记),业务逻辑代码开始
总结一下
以上是基于1.4
版本的,GC
过程在标记过程是STW
的。在1.5
版本里面对GC
做了很大的优化,采用三色标记,将标记过程细化成三段,只有前后的两段是STW
的,极大地缩短了GC
的STW
时间。