参考链接一(外网)
参考二:源码的注释
- golang的gc可以与mutator线程并发执行,而且允许多个gc线程并行。
- 支持并发的mark和sweep,也会使用写屏障。
- 按P来分配隔离的空间来减少碎片内存。
- mcache是没有GC的区域,所以有下面说的把mcache放到central cache中来进行垃圾回收。
- 堆区的回收是通过Arena的bitmap来快速定位。
- Mark阶段会占用25%的CPU,其实大概就是会占用25%的
P
gc的四个阶段
- sweep termination
a. 此过程会引发STW
,所有的P都会到安全点safe-point;
b. 回收未被回收的spans
(内存管理单元),只有gc被强制开始才会有未被回收的spans; - mark
a. 将gc状态从_GCoff
改成_GCmark
,让所有的P
打开write barrier
和GC辅助(就是正在工作的G协助完成gc),把扫描标记的任务放入队列,打开写屏障这段时间是STW
的,write barrier
没打开完成,扫描工作就不会进行;
b. start the world。用写屏障覆盖,并将所有新分配的对象标成黑色;
c. 标记根节点。扫描所有goroutines
栈区指向堆区的指针和全局对象,扫描栈区指针时会将对应的goroutine
暂停,等扫描完成再让它继续运行,将找到的对象放到灰色对象队列中;
d. 扫描所有队列中的灰色对象,将他们标成黑色,并将这些对象包含的对象放到灰色队列中; - mark termination
a.STW
,将gc状态改成_GCmarktermination
,关闭GC辅助和工作线程,计算下一次GC计划;
b. 将mcaches
放到central cache; - sweep
a. 将gc状态改成_GCoff
;
b.Start the world
。从这一刻开始,新分配的对象将是白色的,
c. gc并发地后台清理对象,在这个阶段,gc会用一个后台goroutine挨个清除span,这个阶段当有一个goroutine需要分配一个小对象但是空间不够时,并不会向操作系统申请空间,而是清除小对象,直到够那个goroutine分配。当需要在heap分配大对象时,也是一样的操作。
总之,不能在未被清扫过的span
中搞活动,而且GC过程中mcache
会被放到central cache
中,如果在此期间有goroutine
在mcache
中分配对象,这个goroutine清扫掉这个对象。
当所有的spans
被清除过,GC就会结束
- GC的速率
当新分配的空间与正在使用的空间形成一定比例,就会触发下一次GC,这个比例有参数GOGC
控制,比如GOGC=100,此时空间利用为4M,当空间用到8M时,就会触发GC。 - 写屏障
并发Mark的阶段,黑色节点不会被扫描,但如果这段时间黑色节点又引用其他对象就会导致该对象还是白色,不会加入灰色对象链表。用写屏障来解决这个问题:其实就是在内存每次写入时都加上一段代码,把被引用的对象标成灰色。