Golang使用pprof检查内存泄漏的全过程
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
前言
pprof 是golang提供的一款分析工具,可以分析CPU,内存的使用情况,本篇文章关注它在分析内存泄漏方面的应用。pprof 不能直观显示出某个函数发生了泄漏,它的用途是列出当前内存分配较大的位置,通过比较一段时间pprof的结果,可以发现内存变化。
测试代码
比如我们有下面的函数,在请求中对全局变量分配了一块内存,但没有回收:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | package main import ( "fmt" "log" "net/http" _ "net/http/pprof" // 关键!! "runtime" "sync" ) type UserData struct { Data [] byte } type UserCache struct { mu sync.Mutex Cache map [ string ]*UserData } func (uc *UserCache) clear() { uc.mu.Lock() defer uc.mu.Unlock() uc.Cache = make ( map [ string ]*UserData) } func NewUserCache() *UserCache { return &UserCache{ Cache: make ( map [ string ]*UserData), } } var userCache = NewUserCache() // 分配全局内存 func handleRequest(w http.ResponseWriter, r *http.Request) { userCache.mu.Lock() defer userCache.mu.Unlock() userData := &UserData{ Data: make ([] byte , 1000000 ), } userID := fmt.Sprintf( "%d" , len (userCache.Cache)) // 赋值给全局变量,但没有回收 userCache.Cache[userID] = userData log.Printf( "Added data for user %s. Total users: %d\n" , userID, len (userCache.Cache)) } // 清空全局内存 func handleClear(w http.ResponseWriter, r *http.Request) { userCache.clear() runtime.GC() } func main() { http.HandleFunc( "/leaky-endpoint" , handleRequest) http.HandleFunc( "/clear" , handleClear) http.ListenAndServe( ":8080" , nil ) } |
其中 import "net/http/pprof"
引入了pprof工具,启动服务后,内存分析结果保存在http://localhost:8080/debug/pprof/heap。
发送请求
我们往/leaky-endpoint发送一些请求,触发全局内存分配,可以使用 ab
命令:
-n:请求数量,我们总共发送1000个请求
-c:并发数量,1000个请求通过10个并发线程发送
分析内存
查看内存分配
我们可以通过下面的命令查看分析结果:
1 2 3 4 5 | pprof http: //localhost :8080 /debug/pprof/heap // output: // Entering interactive mode ( type "help" for commands, "o" for options) // (pprof) |
输入上述命令后,会进入命令行交互模式,我们可以输入一系列帮助命令查看内存分析结果:
- top:输出内存分配最多的几个函数
1 2 3 4 5 6 7 8 | (pprof) top Showing nodes accounting for 487.41MB, 100% of 487.41MB total flat flat% sum % cum cum% 487.41MB 100% 100% 487.41MB 100% main.handleRequest 0 0% 100% 487.41MB 100% net /http .(*ServeMux).ServeHTTP 0 0% 100% 487.41MB 100% net /http .(*conn).serve 0 0% 100% 487.41MB 100% net /http .HandlerFunc.ServeHTTP 0 0% 100% 487.41MB 100% net /http .serverHandler.ServeHTTP |
可以看到,handleRequest 分配了最多的内存。这几列的含义是:
- flat:函数自身在采样期间直接消耗的内存,不包括它调用的其他函数的消耗;
- flag%:
flat
值占总消耗资源的百分比,这个百分比是基于整个程序在采样期间的资源消耗来计算的; - sum%: 累积百分比,表示从输出列表顶部开始到当前行为止的所有函数的
flat
百分比之和。这可以帮助你理解最顶部的函数累积起来消耗了多少资源; - cum: 函数自身
flat
,加上它调用的所有函数在采样期间消耗的内存; - cum%:
cum
值占总消耗资源的百分比。这个百分比显示了函数及其所有递归调用的资源消耗在程序总资源消耗中的比重;
- list:列出函数具体分配内存的位置
1 2 3 4 5 6 7 8 9 10 11 | (pprof) list main.handleRequest Total: 487.41MB ROUTINE ======================== main.handleRequest in /home/yuhanyang/project/HelloGo/test_mem_leak/main .go 487.41MB 487.41MB (flat, cum) 100% of Total . . 35:func handleRequest(w http.ResponseWriter, r *http.Request) { . . 36: userCache.mu.Lock() . . 37: defer userCache.mu.Unlock() . . 38: . . 39: userData := &UserData{ 487.41MB 487.41MB 40: Data: make ([]byte, 1000000), . . 41: } |
web:在浏览器中生成并打开 SVG 格式的调用图
png:生成 PNG 格式的调用图
比较内存分配
为了分析内存泄漏,我们往往需要统计一段时间开始和结束时的内存变化情况,在上面的基础上,我们可以再用ab命令触发几次内存分配,然后通过pprof查看:
1 2 3 4 5 6 7 8 9 | >>pprof http: //localhost :8080 /debug/pprof/heap (pprof) top Showing nodes accounting for 3.74GB, 100% of 3.74GB total flat flat% sum % cum cum% 3.74GB 100% 100% 3.74GB 100% main.handleRequest 0 0% 100% 3.74GB 100% net /http .(*ServeMux).ServeHTTP 0 0% 100% 3.74GB 100% net /http .(*conn).serve 0 0% 100% 3.74GB 100% net /http .HandlerFunc.ServeHTTP 0 0% 100% 3.74GB 100% net /http .serverHandler.ServeHTTP |
发现内存较之前增长了,实际上我们可以通过 -diff_base 来比较两次采样结果的差异:
1 2 3 4 5 6 7 8 | // 第一次采样 pprof http: //localhost :8080 /debug/pprof/heap > base.out // 当前采样 pprof http: //localhost :8080 /debug/pprof/heap > current.out // 差异分析 pprof -diff_base=base.out current.out |
查看启动以来的内存分配
上面的pprof实际统计的是采样时刻的内存分配情况,我们可以先清空全局内存,然后再通过top查看,会发现不再有输出了:
1 2 3 4 5 | curl http: //localhost :8080 /clear pprof http: //localhost :8080 /debug/pprof/heap (pprof) top Showing nodes accounting for 0, 0% of 0 total flat flat% sum % cum cum% |
但我们可以通过-sample_index=来控制采样,对于内存分析来说,它有下面几种取值:
- inuse_space:默认,表示程序在执行时刻正在使用的内存量;
- inuse_objects:程序在执行时刻正在使用的对象数量;
- alloc_space:程序自启动以来分配的总内存量,不考虑这些内存是否已经被释放;
- alloc_objects:程序自启动以来分配的总对象数量,不考虑这些对象是否已经被释放;
我们可以查看程序启动以来的总内存分配数据:
1 2 3 4 5 6 7 8 | pprof -sample_index=alloc_space http: //localhost :8080 /debug/pprof/heap (pprof) top Showing nodes accounting for 3842.05MB, 99.44% of 3863.75MB total Dropped 44 nodes (cum <= 19.32MB) Showing top 10 nodes out of 14 flat flat% sum % cum cum% 3832.36MB 99.19% 99.19% 3832.86MB 99.20% main.handleRequest 9.70MB 0.25% 99.44% 19.36MB 0.5% compress /flate .NewWriter |
以上就是Golang使用pprof检查内存泄漏的全过程的详细内容,更多关于Golang pprof内存泄漏的资料请关注脚本之家其它相关文章!

微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
详解Golang中errors包如何返回自定义error类型
这篇文章主要为大家详细介绍了Golang中errors包如何返回自定义error类型,文中的示例代码简洁易懂,有需要的小伙伴可以跟随小编一起学习一下2023-09-09
最新评论