go gc 分析
1 先翻译一下runtime 文档中,关于gc的内容(里面涉及GC日志格式)
···
原文: https://golang.org/pkg/runtime/
翻译参考: https://colobu.com/2016/07/04/dive-into-go-11/#pprof
···
allocfreetrace: 设置 allocfreetrace=1 会监控每次分配,但因每次分配和释放的栈信息(stack trace)
cgocheck: 设置 cgocheck=0 禁用所有cgo检查将Go指针传递给非Go代码是否正确。
cgocheck=1 (缺省值) 轻量级检查。cgocheck=2 重量级检查。
efence: 设置 efence=1 导致分配器 allocator将每个对象分配在一个唯一的页page上,地址不重用。
gccheckmark: 设置 gccheckmark=1 允许垃圾回收器执行并发mark阶段的校验。会导致Stop The World。
gcpacertrace: 设置 gcpacertrace=1 会让来几回收器打印出concurrent pacer的内部状态。
gcshrinkstackoff: 设置 gcshrinkstackoff=1 则禁止将 goroutines 的栈缩小为更小栈。
gcstackbarrieroff: 设置 gcstackbarrieroff=1 禁用stack barriers,会影响垃圾回收器的重复搜索栈的功能。
gcstackbarrierall: 设置 gcstackbarrierall=1 会为每个栈帧安装一 stack barriers。
gcstoptheworld: 设置 gcstoptheworld=1 则禁用并发垃圾回收,每次回收都会触发STW。设置gcstoptheworld=2则禁用垃圾回收后的concurrent sweeping。
gctrace: 设置 gctrace=1导致每次垃圾回收器触发一行日志,包含内存回收的概要信息和暂停的时间。设置gctrace=2起同样的效果,but also repeats each collection。格式如下:
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
where the fields are as follows:
gc # GC id,每次GC加一
@#s 程序启动后的时间,单位秒
#% 程序启动后GC所用的时间比
#+...+# 此次GC所用的wall-clock/CPU时间
#->#-># MB GC开始时的堆大小, GC结束时的堆大小, 活着的(live)堆大小
# MB goal 总的堆大小
# P CPU使用数
垃圾回收分为下面的几个阶段:stop-the-world (STW) sweep termination, concurrent
mark and scan, and STW mark termination。 mark/scan的CPU时间又分为 assist time (GC performed in
line with allocation), background GC time, and idle GC time。
垃圾回收的四个阶段:
Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC
Mark: 扫描所有根对象, 和根对象可以到达的所有对象, 标记它们不被回收
Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)
Sweep: 按标记结果清扫span
如果日志后以"(forced)"结尾,则GC通过runtime.GC()调用执行,此时所有的阶段都是STW.
memprofilerate: 设置 memprofilerate=X 会更新runtime.MemProfileRate的值。0则禁用这个profie。
invalidptr: 默认设为invalidptr=1, 如果指针被赋予一个无效值,会引起程序的崩溃,设置该值为0,会停止该检查,
0只能临时用于查找bug,真正的解决方法是不要把整数类型的值存在指针变量里面。
sbrk: 设置 sbrk=1 会使用实验性的实现替换memory allocator 和 garbage collector。
scavenge: scavenge=1 允许heap scavenger的debug模式。
scheddetail: 设置 schedtrace=X 和 scheddetail=1 会导致goroutine调度器每个X毫秒输出多行调度信息。
schedtrace: 设置 schedtrace=X导致调度器每个X秒输出一行调度器的概要信息。
2 GC 触发时机
gcTriggerHeap: 当前分配的内存达到一定值就触发GC
gcTriggerTime: 当一定时间没有执行过GC就触发GC
gcTriggerCycle: 要求启动新一轮的GC, 已启动则跳过, 手动触发GC的runtime.GC()会使用这个条件
3 gc 具体的过程
3.1 根对象
在GC的标记阶段首先需要标记的就是"根对象", 从根对象开始可到达的所有对象都会被认为是存活的.
根对象包含了全局变量, 各个G的栈上的变量等, GC会先扫描根对象然后再扫描根对象可到达的所有对象.
3.2 三色标记过程
之前自己整理的 https://www.jianshu.com/p/ebf03d9605d0
4 gc 优化案例
4.1 减少分配对象数量(这样就减少了扫描时间)
链接:https://www.zhihu.com/question/21615032/answer/18781477
4.2 一个检测G 长时间占用CPU时间片,导致GC hang住的排查工具
https://github.com/zhanglvmeng/go-tool-trace-greediest-goroutines
4.3 【已验证】io.copybuffer 没传buffer进去,底层自动创建了多个buffer对象,造成内存泄漏,频繁gc。
https://my.oschina.net/u/2950272/blog/1788299
4.4 使用array 替代 map, 减少扫描时间
https://studygolang.com/articles/1720
4.5 多个不同维度的分析
http://guileen.github.io/2016/06/15/how-did-i-optimize-golang-gc/
4.6 减少GC的一些思路
http://www.philo.top/2015/05/29/golangProfilingAndGC2/
4.7 GC排查的步骤
4.8 string 以及[]byte 的GC问题
https://www.520mwx.com/view/35045
5 gc 查看工具
gcvis https://github.com/davecheney/gcvis
参考自 https://colobu.com/2016/07/04/dive-into-go-11/#pprof
6 GC 监控
对于线上GC的监控,基本上读取runtime.MemStats结构中的内容,然后存储到时序数据库中。具体有如下两种获取方式:
// 方式1
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)
// 方式2 json格式
expvar.Get("memstats").String()
7 scavenger
到目前为止,gctrace给出的最有用的信息就是 the heap scavenger的输出.
scvg143: inuse: 8, idle: 104, sys: 113, released: 104, consumed: 8 (MB)
scvg143 表示第143次输出。其他字段,见下图。
图片来源于https://colobu.com/2016/07/04/dive-into-go-11/#pprof
scavenger 的工作就是周期性地打扫heap中无用的操作系统内存分页, 它会向操作系统发出建义,请操作系统回收无用内存页,
当然并不能强迫操作系统立刻就去做回收处理,操作系统可以忽略此建义,或是延迟回收,比如直到可分配的空闲内存不够的时候。
scavenger输出的信息是我们了解go程序虚拟内存空间
使用情况的最好方式, 当然你也可以通过其它工具,如free, top来获到这些信息,
不过你应用信任scavenger.
8 参考文献
非常详细的文章https://yq.aliyun.com/blog/573819
gc 线上监控: http://kuring.me/post/golang-gc/
scavenger: https://studygolang.com/articles/6346