图中方块越大便是占用的内存越多,方块中连线越粗表示耗时越多
内存泄露误区
我在排查内存泄露时,当我把goroutine阻塞解决后,通过linux 的 top 命令查看Gateway内存占用时,发现内存没有降下来,一时让我陷入困惑,为啥goroutine 都结束了,为啥内存还不释放呢?直到我在网上找到了这篇文章 传送门 </br>
这位大神写的golang 相关的文章,是目前我在网上找到的最牛逼的之一,文章不光有深度,而且通俗易懂。
重新验证
修改上面的代码
package main
import (
"math/rand"
"net/http"
_ "net/http/pprof"
"sync"
"time"
)
func main() {
go func() {
http.ListenAndServe("0.0.0.0:8090", nil)
}()
var wg sync.WaitGroup
wg.Add(1)
for i := 0; i < 10000; i++ {
go one()
}
wg.Wait()
}
func one() {
var a []int64
for i := 0; i < 10000; i++ {
a = append(a, rand.Int63())
}
time.Sleep(time.Second * 1)
}
现在 go 出来的的函数 one 不再一直阻塞,而是只会阻塞5秒
把程序运行起来看一下
等一段时间后,发现goroutine数量已经下来了,说明阻塞的协程都已经结束了,如图
但是通过任务管理器中,看到程序还是占用着大量的内存
任务管理器
这时点击 heap 查看具体的堆内存情况,拉到最低下,看到一堆参数
参数很多,但是重点关注被框出来的那几个就好了,详细的分析在大神的文章中有分析,在go的库文件中也能找到,里面有详细的注释。</br>
路径 src->runtime->mstats.go 文件中有 MemStats 的定义和注释
这些参数的单位都是字节
TotalAlloc : 所有对象分配的总和,整个程序运行期间,只增不减
HeapAlloc : 分配的堆对象的字节数,包括可访问的对象和未被GC回收的不可访问的对象,这个值会动态变化,分配对象时增加,回收对象后减少
HeapIdle : 闲置的span中的字节数,这些span中已经没有对象了(不用了),但是现在还没有还给操作系统,这些span可以重新用来分配heap和stack。HeapIdle 减去 HeapReleased 的值可以当作 "可以返回到操作系统但由运行时保留的内存量",也就是说,go的runtime可以在不像操作系统申请内存的情况下,也可以分配heap空间,这样可以提高程序性能
HeapInuse : 正在使用的span的字节大小
HeapReleased : 是返还给操作系统的物理内存的字节数
回到我们的测试程序中,当所有的goroutine都结束时,GC会开始回收切片,但是被回收的内存不会直接换给操作系统,而是由go的runtime暂时保管(也就是 HeapIdle 值会变大),接下来如果再次需要分配空间,go的runtime可以不向操作系统申请内存,直接从自己保管的闲置内存中分配,这样可以提高程序性能。至于GO的runtime什么时候把这部分内存还给操作系统,不同的分配策略和不同的系统不太一样,这个有点深,我还没有深入研究这些。不过上面传送门 的文中有介绍 MADV_FREE 有兴趣可以自己学习一下
总结
go 虽然有GC,但是使用不当也会导致内存泄露
不同的操作系统和不用策略,会导致go程序的内存已经被回收了,但是没有及时的归还给操作系统
由于水平有限,文中如有谬处,还请在评论区不吝赐教
本文来自:51CTO博客
感谢作者:mb604dca6061f71
查看原文:go 使用pprof 排查内存泄露
相关阅读 >>
手撸Golang 基本数据结构与算法 图的最短路径 贝尔曼-福特算法
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。