- package main
- func allocate() {
- _ = make([]byte, 1<<20)
- }
- func main() {
- for n := 1; n < 100000; n++ {
- allocate()
- }
- }
方式1:GODEBUG=gctrace=1
我们首先可以通过
- $ go build -o main
- $ 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: 多核优势不明显
- scvg: 8 KB released
- 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:
- package main
- func main() {
- f, _ := os.Create("trace.out")
- defer f.Close()
- trace.Start(f)
- defer trace.Stop()
- (...)
- }
并通过
- $ go tool trace trace.out
- 2019/12/30 15:50:33 Parsing trace...
- 2019/12/30 15:50:38 Splitting trace...
- 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 的状态:
- func printGCStats() {
- t := time.NewTicker(time.Second)
- s := debug.GCStats{}
- for {
- select {
- case <-t.C:
- debug.ReadGCStats(&s)
- fmt.Printf("gc %d last@%v, PauseTotal %v\n", s.NumGC, s.LastGC, s.PauseTotal)
- }
- }
- }
- func main() {
- go printGCStats()
- (...)
- }
我们能够看到如下输出:
- $ go run main.go
- gc 4954 last@2019-12-30 15:19:37.505575 +0100 CET, PauseTotal 29.901171ms
- gc 9195 last@2019-12-30 15:19:38.50565 +0100 CET, PauseTotal 77.579622ms
- gc 13502 last@2019-12-30 15:19:39.505714 +0100 CET, PauseTotal 128.022307ms
- gc 17555 last@2019-12-30 15:19:40.505579 +0100 CET, PauseTotal 182.816528ms
- gc 21838 last@2019-12-30 15:19:41.505595 +0100 CET, PauseTotal 246.618502ms
方式4:runtime.ReadMemStats
除了使用 debug 包提供的方法外,还可以直接通过运行时的内存相关的 API 进行监控:
- func printMemStats() {
- t := time.NewTicker(time.Second)
- s := runtime.MemStats{}
- for {
- select {
- case <-t.C:
- runtime.ReadMemStats(&s)
- 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))
- }
- }
- }
- func main() {
- go printMemStats()
- (...)
- }
- $ go run main.go
- gc 4887 last@2019-12-30 15:44:56 +0100 CET, next_heap_size@4MB
- gc 10049 last@2019-12-30 15:44:57 +0100 CET, next_heap_size@4MB
- gc 15231 last@2019-12-30 15:44:58 +0100 CET, next_heap_size@4MB
- gc 20378 last@2019-12-30 15:44:59 +0100 CET, next_heap_size@6MB
当然,后两种方式能够监控的指标很多,读者可以自行查看 debug.GCStats [2] 和runtime.MemStats [3] 的字段,这里不再赘述。