Golang GC如何观察

  1. package main
  2. func allocate() {
  3. _ = make([]byte, 1<<20)
  4. }
  5. func main() {
  6. for n := 1; n < 100000; n++ {
  7. allocate()
  8. }
  9. }
方式1:GODEBUG=gctrace=1
我们首先可以通过
  1. $ go build o main
  2. $ GODEBUG=gctrace=1 ./main
含义由下表所示:
字段
含义
gc 2
第二个 GC 周期
0.001
程序开始后的 0.001 秒
2%
该 GC 周期中 CPU 的使用率
0.018
标记开始时, STW 所花费的时间(wall clock)
1.1
标记过程中,并发标记所花费的时间(wall clock)
0.029
标记终止时, STW 所花费的时间(wall clock)
0.22
标记开始时, STW 所花费的时间(cpu time)
0.047
标记过程中,标记辅助所花费的时间(cpu time)
0.074
标记过程中,并发标记所花费的时间(cpu time)
0.048
标记过程中,GC 空闲的时间(cpu time)
0.34
标记终止时, STW 所花费的时间(cpu time)
4
标记开始时,堆的大小的实际值
7
标记结束时,堆的大小的实际值
3
标记结束时,标记为存活的对象大小
5
标记结束时,堆的大小的预测值
12
P 的数量
wall clock 是指开始执行到完成所经历的实际时间,包括其他程序和本程序所消耗的时间;cpu time 是指特定程序使用 CPU 的时间;他们存在以下关系:
  • wall clock < cpu time: 充分利用多核
  • wall clock ≈ cpu time: 未并行执行
  • wall clock > cpu time: 多核优势不明显
  1. scvg: 8 KB released
  2. scvg: inuse: 3, idle: 60, sys: 63, released: 57, consumed: 6 (MB)
含义由下表所示:
字段
含义
8 KB released
向操作系统归还了 8 KB 内存
3
已经分配给用户代码、正在使用的总内存大小 (MB)
60
空闲以及等待归还给操作系统的总内存大小(MB)
63
通知操作系统中保留的内存大小(MB)
57
已经归还给操作系统的(或者说还未正式申请)的内存大小(MB)
6
已经从操作系统中申请的内存大小(MB)
方式2:go tool trace
go tool trace 的主要功能是将统计而来的信息以一种可视化的方式展示给用户。要使用此工具,可以通过调用 trace API:
  1. package main
  2. func main() {
  3. f, _ := os.Create(“trace.out”)
  4. defer f.Close()
  5. trace.Start(f)
  6. defer trace.Stop()
  7. (…)
  8. }
并通过
  1. $ go tool trace trace.out
  2. 2019/12/30 15:50:33 Parsing trace
  3. 2019/12/30 15:50:38 Splitting trace
  4. 2019/12/30 15:50:45 Opening browser. Trace viewer is listening on http://127.0.0.1:51839
命令来启动可视化界面:
选择第一个链接可以获得如下图示:
右上角的问号可以打开帮助菜单,主要使用方式包括:
  • w/s 键可以用于放大或者缩小视图
  • a/d 键可以用于左右移动
  • 按住 Shift 可以选取多个事件
方式3:debug.ReadGCStats
此方式可以通过代码的方式来直接实现对感兴趣指标的监控,例如我们希望每隔一秒钟监控一次 GC 的状态:
  1. func printGCStats() {
  2. t := time.NewTicker(time.Second)
  3. s := debug.GCStats{}
  4. for {
  5. select {
  6. case <-t.C:
  7. debug.ReadGCStats(&s)
  8. fmt.Printf(“gc %d last@%v, PauseTotal %v\n”, s.NumGC, s.LastGC, s.PauseTotal)
  9. }
  10. }
  11. }
  12. func main() {
  13. go printGCStats()
  14. (…)
  15. }
我们能够看到如下输出:
  1. $ go run main.go
  2. gc 4954 last@20191230 15:19:37.505575 +0100 CET, PauseTotal 29.901171ms
  3. gc 9195 last@20191230 15:19:38.50565 +0100 CET, PauseTotal 77.579622ms
  4. gc 13502 last@20191230 15:19:39.505714 +0100 CET, PauseTotal 128.022307ms
  5. gc 17555 last@20191230 15:19:40.505579 +0100 CET, PauseTotal 182.816528ms
  6. gc 21838 last@20191230 15:19:41.505595 +0100 CET, PauseTotal 246.618502ms
方式4:runtime.ReadMemStats
除了使用 debug 包提供的方法外,还可以直接通过运行时的内存相关的 API 进行监控:
  1. func printMemStats() {
  2. t := time.NewTicker(time.Second)
  3. s := runtime.MemStats{}
  4. for {
  5. select {
  6. case <-t.C:
  7. runtime.ReadMemStats(&s)
  8. fmt.Printf(“gc %d last@%v, next_heap_size@%vMB\n”, s.NumGC, time.Unix(int64(time.Duration(s.LastGC).Seconds()), 0), s.NextGC/(1<<20))
  9. }
  10. }
  11. }
  12. func main() {
  13. go printMemStats()
  14. (…)
  15. }
  1. $ go run main.go
  2. gc 4887 last@20191230 15:44:56 +0100 CET, next_heap_size@4MB
  3. gc 10049 last@20191230 15:44:57 +0100 CET, next_heap_size@4MB
  4. gc 15231 last@20191230 15:44:58 +0100 CET, next_heap_size@4MB
  5. gc 20378 last@20191230 15:44:59 +0100 CET, next_heap_size@6MB
当然,后两种方式能够监控的指标很多,读者可以自行查看 debug.GCStats [2] 和runtime.MemStats [3] 的字段,这里不再赘述。

发表评论

电子邮件地址不会被公开。 必填项已用*标注