一次go内存泄漏调试
go性能调试工具
虽然不一定都需要
- gv,go自带的pprof命令,实际上是
Graphviz,搜索安装就可以了- web,同gv,不过是在浏览器中显示
- go-torch,火焰图
案例背景
源自于同事的socket连接服务,服务启动一段时间后,内存飙升,呈现累积不下的现象,初步判断为内存泄漏(top下单个服务占用29G内存).
问题定位
- 找到函数
1
go tool pprof --inuse_space {{host}}/debug/pprof/heap
结果如下:

-
分析代码(由于代码变动,实际上137行移动到140行)

- 很明显是函数中
msgData泄漏了.但是一开始在分析怎么泄漏的时候走了不少弯路 - 因为
conn是socket连接,所以在一开始Read的时候,读出的数据包是要自定义解析的,初步判定msgLen值过大引起分配过大,至于为什么过大(是客户端传错了还是怎么的,暂不考虑,主要是也没打日志). - 那么为什么分配过大会引起内存泄漏?因为
go是自带gc的语言 - 弯路: 怀疑
go线程泄漏(因为想当然的认为接下来的错误没有问题),于是去分析goroutine
- 一顿分析之后还是觉得不是
goroutine的泄漏,很简单,因为前面内存泄漏是指向这个函数的 - 查看该函数相关的阻塞,当然肯定不是
block里面的阻塞(为什么呢?因为这个函数很简单,msgData泄漏的唯一可能是这个变量没有被使用,于是怀疑上是阻塞).重新打开goroutine的pprof界面,搜索该方法及文件名,很快发现一些IO Wait状态线程,于是确定是阻塞

- 那么,唯一可能的阻塞,也正如
heap文件指向的 140(137)行之后的io.ReadFull方法 - 一开始想到阻塞的时候甚至用byte.NewReader配合写了一个测试,然后否定了

- 再次定位确认的时候,只好查阅官方文档.话外的意思就是说,一开开始读到了,不够了,要遇到EOF才返回.

- 于是想到之前的测试用例,读完是有EOF的,本着严谨的态度确认一下去
bytes包的文档,当然下面给出的是Buffer的注释(Reader下没有)

- 在去看一下
net.Conn下的Read方法的注释,于是确认了这里这样读是会阻塞的

- 很明显是函数中
解决问题
- 设置最大的buffer值.该同学设置过,但是使用默认的设置值过大,没有效果.这样可以避免内存阻塞过大,但是解决不了阻塞问题
- 使用SetReadDeadline读取数据,这样就要在143行读取之后,再次将该值设置成
0或者比较大的一个值,比较麻烦.