Go垃圾回收机制

上一篇分析了Go的内存分配机制,本文将简单分析一下Go的垃圾回收机制。本文是《循序渐进go语言》第五篇,本文也是刚刚开始学习Go语言,如果有理解不到位的地方,欢迎批评指正,也希望这篇文章对你有所帮助~

1 Go垃圾回收的主要流程

1.1 概述

Go Gc的基本特征是"非分代、非紧缩、写屏障、并发标记清理"。
Go使用了 三色标记和写屏障
步骤如下:

  • 起初所有对象都是白色的
  • 扫描找出所有可达对象,标记为灰色,放入待处理队列
  • 从队列提取灰色对象,将其引用对象标记为灰色,自身标记为黑色
  • 写屏障用来监视对象内存的修改,重新标色或放回待处理队列。

扫描完成,剩余的不是白色就是黑色,分别代表待回收和活跃对象,清理操作只需要将白色对象回收即可,所以清理这一步可以并发操作。

1.2 并发标记清理

Go的Gc采用的 “标记-清扫(Mark and Sweep)算法”。
主要分了两步:

  • 扫描标记
  • 清扫。
1.2.1 扫描标记

标记又分了如下几步:
(1)gc开始,所有对象都被标记为白色,并且打开写屏障(用于监控对象的变更)
这一步因为需要打开写屏障,所有需要STW 大约10-30微秒

step 1.png

如下2-4 步,为并发执行标记,虽然不会STW,但是会消耗cpu资源进行扫描,据资料称为25%
(2)扫描所有可达对象,标记为灰色

step 2.png

(3)从灰色对象中找到其引用的对象标记为灰色,将其自身标记为黑色


step 3.png

(4)监控对象中的内存修改(包括被对象新引用的内存、新分配的内存),持续上一步操作,直到灰色对象不存在为止
内存变更或者引用了新内存,这些内存都会被直接标记为灰色,等待进一步标记

step 4.png

(5)标记完毕,关闭。
这一步需要关闭写屏障,需要STW,大约60-90微秒

step 5.png

1.2.2 清理

将白色内存清理即可。
清理过程有两种方案,这个要看不同的Go版本实现:
(1) 在堆内存分配时执行清理,此时性能的损耗不算做GC的消耗,而算是内存分配的消耗。
(2)专门的线程进行清理。

2 性能损耗

虽然上面章节已经介绍了性能损耗的主要方面,鉴于GC对运行时间敏感的服务影响比较大,这儿有必要专门列一下。Gc 性能算好主要体现在如下两个方面:
(1)窃取CPU 的处理能力,使CPU不能全力处理应用请求,比如辅助回收等。

  • 辅助回收是让用户线程参与垃圾回收。因为有些时候内存分配的速度可能远远大于后台的标记,这会引起堆的恶性扩张,甚至让垃圾回收永远无法完成。

(2)标记开始跟结束阶段的STW。

3 怎么降低GC延迟

降低内存压力,这儿的压力是指应用在规定时间内分配内存的速度。降低GC延迟的主要方式是从应用中辨别和去掉不需要的内存分配

4 Gc日志分析

如果要看Gc日志,采用如下指令:

GODEGUB=gctrace=1 ./testProcess

其中 ./testProcess 为编译好的go程序。
Gc日志格式如下:

gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms clock, 0.70+2.5/1.5/0+0.99 ms CPU, 7->11->6 MB, 10 MB Goal, 12 P
// General
gc 1404     : 自程序启动以来,1404 的 GC 运行 
@6.068s     : 自程序启动至此总共 6s
11%         : 到目前为止,可用 CPU 的 11% 被用于 GC
// Wall-Clock
0.058ms     : STW     : 标记开始,开启写障碍
1.2ms       : 并发     : 标记中
0.083ms     : STW     : 标记结束 - 关闭写障碍并清除

// CPU Time
0.70ms      : STW        : 标记开始
2.5ms       : 并发        : 辅助标记时间 (GC 按照分配执行 )
1.5ms       : 并发        : 标记 - 后台 GC 时间
0ms         : 并发        : 标记 - 空闲 GC 时间
0.99ms      : STW        : 标记结束

// Memory
7MB         : 标记开始前使用中的堆内存
11MB        : 标记完成后使用中的堆内存
6MB         : 标记完成后被标记为存活的堆内存
10MB        : 标记完成后使用中的堆内存收集目标

// Threads
12P         : 用于运行 Gorouitne 的物理调度器或线程的数量
memory.png
// Memory
7MB         : 标记开始前使用中的堆内存
11MB        : 标记完成后使用中的堆内存
6MB         : 标记完成后被标记为存活的堆内存
10MB        : 标记完成后使用中的堆内存收集目标

标记前为7M ,标记后为11M,说明标记过程中分配了4M内存。在标记工作完成被标记为存活的内存大小为6M。这意味着下次回收开始前应用可以增加使用的堆内存到12M(6M增加了100%),但是实际情况很可能不是12M,如果回收器认为早点开始回收会好一点,就会提前,比如下次可能到8M就开始回收了。

5 步调

  • 回收器具有确定何时开始收集的步调算法。算法依赖于回收器用于收集有关正在运行的应用的信息以及应用在堆上分配的压力的反馈循环。压力可以被定义为在指定时间范围内应用分配堆内存的速度。正是压力决定了回收器需要运行的速度。
  • 一种误解是认为降低回收器步调是改善性能的一种方法。这个想法是,如果你能延缓下次回收的开始,那么你也能延缓它所造成的延时。对回收器友好并不是要降慢其步调。

对回收器友好跟放慢垃圾回收的步调无关,而是跟在垃圾回收的间隔或期间让更多的工作做完有关。

6 总结

本文总结了Go 垃圾回收的主要流程步骤,并且讲解了垃圾回收的性能损耗点,以及如何降低GC延迟。最后对Gc日志进行了分析。

7 参考文献

1 https://www.jianshu.com/p/eb6b3aff9ca5?utm_campaign=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com
2 https://studygolang.com/articles/21569
3 《Go语言学习笔记》
4、为什么Go还没实现分代和紧凑GC https://studygolang.com/articles/12196

8 其他

本文是《循序渐进go语言》的第五篇-《Go垃圾回收机制》。
如果有疑问,可以直接留言,也可以关注公众号 “链人成长chainerup” 提问留言,或者加入知识星球“链人成长” 与我深度链接~

你可能感兴趣的:(Go垃圾回收机制)