用 GODEBUG 看 GC

image

原文地址:用 GODEBUG 看 GChtml

什么是 GC

在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序再也不使用的对象及其占用的内存。而最先 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 wikipedia)。git

为何要 GC

手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接致使程序不稳定(持续泄露)甚至直接崩溃。github

GC 带来的问题

硬要说会带来什么问题的话,也就数你们最关注的 Stop The World(STW),STW 代指在执行某个垃圾回收算法的某个阶段时,须要将整个应用程序暂停去处理 GC 相关的工做事项。例如:golang

行为 会不会 STW 为何
标记开始 在开始标记时,准备根对象的扫描,会打开写屏障(Write Barrier) 和 辅助GC(mutator assist),而回收器和应用程序是并发运行的,所以会暂停当前正在运行的全部 Goroutine。
并发标记中 不会 标记阶段,主要目的是标记堆内存中仍在使用的值。
标记结束 在完成标记任务后,将从新扫描部分根对象,这时候会禁用写屏障(Write Barrier)和辅助GC(mutator assist),而标记阶段和应用程序是并发运行的,因此在标记阶段可能会有新的对象产生,所以在从新扫描时须要进行 STW。

如何调整 GC 频率

能够经过 GOGC 变量设置初始垃圾收集器的目标百分比值,对比的规则为当新分配的数值与上一次收集后剩余的实时数值的比例达到设置的目标百分比时,就会触发 GC,默认值为 GOGC=100。若是将其设置为 GOGC=off 能够彻底禁用垃圾回收器,要不试试?算法

简单来说就是,GOGC 的值设置的越大,GC 的频率越低,但每次最终所触发到 GC 的堆内存也会更大。c#

各版本 GC 状况

版本 GC 算法 STW 时间
Go 1.0 STW(强依赖 tcmalloc) 百ms到秒级别
Go 1.3 Mark STW, Sweep 并行 百ms级别
Go 1.5 三色标记法, 并发标记清除。同时运行时从 C 和少许汇编,改成 Go 和少许汇编实现 10-50ms级别
Go 1.6 1.5 中一些与并发 GC 不协调的地方更改,集中式的 GC 协调协程,改成状态机实现 5ms级别
Go 1.7 GC 时由 mark 栈收缩改成并发,span 对象分配机制由 freelist 改成 bitmap 模式,SSA引入 ms级别
Go 1.8 混合写屏障(hybrid write barrier), 消除 re-scanning stack sub ms
Go 1.12 Mark Termination 流程优化 sub ms, 但几乎减小一半

注:资料来源于 @boya 在深圳 Gopher Meetup 的分享。并发

GODEBUG

GODEBUG 变量能够控制运行时内的调试变量,参数以逗号分隔,格式为:name=val。本文着重点在 GC 的观察上,主要涉及 gctrace 参数,咱们经过设置 gctrace=1 后就能够使得垃圾收集器向标准错误流发出 GC 运行信息。post

涉及术语

  • mark:标记阶段。
  • markTermination:标记结束阶段。
  • mutator assist:辅助 GC,是指在 GC 过程当中 mutator 线程会并发运行,而 mutator assist 机制会协助 GC 作一部分的工做。
  • heap_live:在 Go 的内存管理中,span 是内存页的基本单元,每页大小为 8kb,同时 Go 会根据对象的大小不一样而分配不一样页数的 span,而 heap_live 就表明着全部 span 的总大小。
  • dedicated / fractional / idle:在标记阶段会分为三种不一样的 mark worker 模式,分别是 dedicated、fractional 和 idle,它们表明着不一样的专一程度,其中 dedicated 模式最专一,是完整的 GC 回收行为,fractional 只会干部分的 GC 行为,idle 最轻松。这里你只须要了解它是不一样专一程度的 mark worker 就行了,详细介绍咱们能够等后续的文章。

演示代码

func main() {
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func(wg *sync.WaitGroup) {
            var counter int
            for i := 0; i < 1e10; i++ {
                counter++
            }
            wg.Done()
        }(&wg)
    }

    wg.Wait()
}

gctrace

$ GODEBUG=gctrace=1 go run main.go    
gc 1 @0.032s 0%: 0.019+0.45+0.003 ms clock, 0.076+0.22/0.40/0.80+0.012 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 2 @0.046s 0%: 0.004+0.40+0.008 ms clock, 0.017+0.32/0.25/0.81+0.034 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 3 @0.063s 0%: 0.004+0.40+0.008 ms clock, 0.018+0.056/0.32/0.64+0.033 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 4 @0.080s 0%: 0.004+0.45+0.016 ms clock, 0.018+0.15/0.34/0.77+0.065 ms cpu, 4->4->1 MB, 5 MB goal, 4 P
gc 5 @0.095s 0%: 0.015+0.87+0.005 ms clock, 0.061+0.27/0.74/1.8+0.023 ms cpu, 4->4->1 MB, 5 MB goal, 4 P
gc 6 @0.113s 0%: 0.014+0.69+0.002 ms clock, 0.056+0.23/0.48/1.4+0.011 ms cpu, 4->4->1 MB, 5 MB goal, 4 P
gc 7 @0.140s 1%: 0.031+2.0+0.042 ms clock, 0.12+0.43/1.8/0.049+0.17 ms cpu, 4->4->1 MB, 5 MB goal, 4 P
...

格式

gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P

含义

  • gc#:GC 执行次数的编号,每次叠加。
  • @#s:自程序启动后到当前的具体秒数。
  • #%:自程序启动以来在GC中花费的时间百分比。
  • #+...+#:GC 的标记工做共使用的 CPU 时间占总 CPU 时间的百分比。
  • #->#-># MB:分别表示 GC 启动时, GC 结束时, GC 活动时的堆大小.
  • #MB goal:下一次触发 GC 的内存占用阈值。
  • #P:当前使用的处理器 P 的数量。

案例

gc 7 @0.140s 1%: 0.031+2.0+0.042 ms clock, 0.12+0.43/1.8/0.049+0.17 ms cpu, 4->4->1 MB, 5 MB goal, 4 P
  • gc 7:第 7 次 GC。
  • @0.140s:当前是程序启动后的 0.140s。
  • 1%:程序启动后到如今共花费 1% 的时间在 GC 上。
  • 0.031+2.0+0.042 ms clock:优化

    • 0.031:表示单个 P 在 mark 阶段的 STW 时间。
    • 2.0:表示全部 P 的 mark concurrent(并发标记)所使用的时间。
    • 0.042:表示单个 P 的 markTermination 阶段的 STW 时间。
  • 0.12+0.43/1.8/0.049+0.17 ms cpu:spa

    • 0.12:表示整个进程在 mark 阶段 STW 停顿的时间。
    • 0.43/1.8/0.049:0.43 表示 mutator assist 占用的时间,1.8 表示 dedicated + fractional 占用的时间,0.049 表示 idle 占用的时间。
    • 0.17ms:0.17 表示整个进程在 markTermination 阶段 STW 时间。
  • 4->4->1 MB:

    • 4:表示开始 mark 阶段前的 heap_live 大小。
    • 4:表示开始 markTermination 阶段前的 heap_live 大小。
    • 1:表示被标记对象的大小。
  • 5 MB goal:表示下一次触发 GC 回收的阈值是 5 MB。
  • 4 P:本次 GC 一共涉及多少个 P。

总结

经过本章节咱们掌握了使用 GODEBUG 查看应用程序 GC 运行状况的方法,只要用这种方法咱们就能够观测不一样状况下 GC 的状况了,甚至能够作出很是直观的对比图,你们不妨尝试一下。

关联文章

参考

相关文章
相关标签/搜索