前言
本篇文章介绍如何分析golang程序的内存使用情况。包含以下几种方法的介绍:
- 执行前添加系统环境变量
GODEBUG='gctrace=1'
来跟踪打印垃圾回收器信息 - 在代码中使用runtime.ReadMemStats来获取程序当前内存的使用情况
- 使用pprof工具
注意,本篇文章前后有关联,需要顺序阅读。
从十来行的demo开始
1 | package main |
在我的centos上编译并运行
1 | go build -o snippet_mem && ./snippet_mem |
打印如下信息:
1 | 2019/04/06 14:23:16 start. |
使用top命令查看snippet_mem进程的内存RSS占用为470M。
1 | top -p $(pidof snippet_mem) |
分析:
直观上来说,这个程序在f()函数执行完后,切片的内存应该被释放,不应该占用470M那么大。
下面让我们使用一些手段来分析程序的内存使用情况。
GODEBUG中的gctrace
我们在执行demo程序之前添加环境变量GODEBUG='gctrace=1'
来跟踪打印垃圾回收器信息
1 | go build -o snippet_mem && GODEBUG='gctrace=1' ./snippet_mem |
在分析demo程序的输出信息之前,先把gctrace输出信息的格式以及字段的含义放前面,一会我们的分析要基于这部分内容。
1 | gctrace输出信息的格式以及字段的含义对应的官方文档:https://godoc.org/runtime |
打印如下信息:
1 | 2019/04/06 14:28:26 start. |
这里顺便一提,gc的打印信息和demo程序log打印的信息是并行往标准错误输出打印的,所以可能会乱,上面所贴的打印信息的倒数第3、4行是我自己手动重排了,重排前的信息如下:
1 | gc 24 @0.446s 0%: 2019/04/06 14:28:27 force gc. |
demo程序之后会每隔一段时间打印一些gc信息,汇总如下:
1 | GC forced |
分析:
先看在f()函数执行完后立即打印的gc 24
那行的信息。444->444->0 MB, 445 MB goal
表示垃圾回收器已经把444M的内存标记为非活跃的内存。
再看0.1秒之后的gc 25
。0->0->0 MB, 8 MB goal
表示垃圾回收器中的全局堆内存大小由445M下降为8M。
结论:在f()函数执行完后,demo程序中的切片容器所申请的堆空间都被垃圾回收器回收了。
但是此时top显示内存依然占用470M。
结论:垃圾回收器回收了应用层的内存后,(可能)并不会立即将内存归还给系统。
接下来看scvg相关的信息。该信息在demo程序每运行一段时间后打印一次。
scvg0时consumed为512M。此时内存还没有归还给系统。
scvg1时consumed为0,并且scvg1的released=(scvg0 released + scvg0 consumed)
。此时内存已归还给系统。
我们通过top命令查看,内存占用下降为38M。
之后打印的scvg信息不再有变化。
结论:垃圾回收器在一段时间后,(可能)会将回收的内存归还给系统。
到这里,我们对GODEBUG中的gctrace的用法已经介绍完毕了。
实时上,我们最前面的疑问也解决了。
但是我们接下来依然会使用另外几次方法来分析我们的domo程序。
runtime.ReadMemStats
我们稍微修改一下demo程序,在一些执行流程上以及f()函数执行完后每10秒使用runtime.ReadMemStats
获取内存使用情况。
1 | package main |
打印如下信息:
1 | 2019/04/06 17:37:52 start. |
可以看到,打印done.
之后那条trace信息,Alloc已经下降,即内存已被垃圾回收器回收。在2019/04/06 17:42:52
和2019/04/06 17:43:02
的两条trace信息中,HeapReleased开始上升,即垃圾回收器把内存归还给系统。距离打印done.
时有5分钟时间间隔。
另外,MemStats还可以获取其它哪些信息以及字段的含义可以参见官方文档:
http://golang.org/pkg/runtime/#MemStats
使用pprof工具
在网页上查看内存使用情况,需在代码中添加两行代码
1 | import( |
然后就可以使用浏览器打开以下地址查看内存信息 http://127.0.0.1:10000/debug/pprof/heap?debug=1
使用此方法,除了有MemStats的信息,还有申请内存发生在哪些函数的信息。
总结
golang的垃圾回收器在回收了应用层的内存后,有可能并不会立即将回收的内存归还给操作系统。
如果我们要观察应用层代码使用的内存大小,可以观察Alloc
字段。
如果我们要观察程序从系统申请的内存以及归还给系统的情况,可以观察HeapIdle
和HeapReleased
字段。
以上3种方法,都是获取了程序的MemStats信息。区别是:第一种完全不用修改程序,第二种可以在指定位置获取信息,第三种可以查看具体哪些函数申请了内存。
结尾
参考链接
- https://blog.golang.org/profiling-go-programs
- https://golang.org/cmd/pprof/
- https://golang.org/pkg/net/http/pprof/
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/24169/