Golang使用pprof檢查內(nèi)存泄漏的全過(guò)程
前言
pprof 是golang提供的一款分析工具,可以分析CPU,內(nèi)存的使用情況,本篇文章關(guān)注它在分析內(nèi)存泄漏方面的應(yīng)用。pprof 不能直觀顯示出某個(gè)函數(shù)發(fā)生了泄漏,它的用途是列出當(dāng)前內(nèi)存分配較大的位置,通過(guò)比較一段時(shí)間pprof的結(jié)果,可以發(fā)現(xiàn)內(nèi)存變化。
測(cè)試代碼
比如我們有下面的函數(shù),在請(qǐng)求中對(duì)全局變量分配了一塊內(nèi)存,但沒(méi)有回收:
package main import ( "fmt" "log" "net/http" _ "net/http/pprof" // 關(guān)鍵??! "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() // 分配全局內(nèi)存 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)) // 賦值給全局變量,但沒(méi)有回收 userCache.Cache[userID] = userData log.Printf("Added data for user %s. Total users: %d\n", userID, len(userCache.Cache)) } // 清空全局內(nèi)存 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工具,啟動(dòng)服務(wù)后,內(nèi)存分析結(jié)果保存在http://localhost:8080/debug/pprof/heap。
發(fā)送請(qǐng)求
我們往/leaky-endpoint發(fā)送一些請(qǐng)求,觸發(fā)全局內(nèi)存分配,可以使用 ab
命令:
ab -n 1000 -c 10 http://localhost:8080/leaky-endpoint
-n:請(qǐng)求數(shù)量,我們總共發(fā)送1000個(gè)請(qǐng)求
-c:并發(fā)數(shù)量,1000個(gè)請(qǐng)求通過(guò)10個(gè)并發(fā)線程發(fā)送
分析內(nèi)存
查看內(nèi)存分配
我們可以通過(guò)下面的命令查看分析結(jié)果:
pprof http://localhost:8080/debug/pprof/heap // output: // Entering interactive mode (type "help" for commands, "o" for options) // (pprof)
輸入上述命令后,會(huì)進(jìn)入命令行交互模式,我們可以輸入一系列幫助命令查看內(nèi)存分析結(jié)果:
- top:輸出內(nèi)存分配最多的幾個(gè)函數(shù)
(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 分配了最多的內(nèi)存。這幾列的含義是:
- flat:函數(shù)自身在采樣期間直接消耗的內(nèi)存,不包括它調(diào)用的其他函數(shù)的消耗;
- flag%:
flat
值占總消耗資源的百分比,這個(gè)百分比是基于整個(gè)程序在采樣期間的資源消耗來(lái)計(jì)算的; - sum%: 累積百分比,表示從輸出列表頂部開(kāi)始到當(dāng)前行為止的所有函數(shù)的
flat
百分比之和。這可以幫助你理解最頂部的函數(shù)累積起來(lái)消耗了多少資源; - cum: 函數(shù)自身
flat
,加上它調(diào)用的所有函數(shù)在采樣期間消耗的內(nèi)存; - cum%:
cum
值占總消耗資源的百分比。這個(gè)百分比顯示了函數(shù)及其所有遞歸調(diào)用的資源消耗在程序總資源消耗中的比重;
- list:列出函數(shù)具體分配內(nèi)存的位置
(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:在瀏覽器中生成并打開(kāi) SVG 格式的調(diào)用圖
png:生成 PNG 格式的調(diào)用圖
比較內(nèi)存分配
為了分析內(nèi)存泄漏,我們往往需要統(tǒng)計(jì)一段時(shí)間開(kāi)始和結(jié)束時(shí)的內(nèi)存變化情況,在上面的基礎(chǔ)上,我們可以再用ab命令觸發(fā)幾次內(nèi)存分配,然后通過(guò)pprof查看:
>>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
發(fā)現(xiàn)內(nèi)存較之前增長(zhǎng)了,實(shí)際上我們可以通過(guò) -diff_base 來(lái)比較兩次采樣結(jié)果的差異:
// 第一次采樣 pprof http://localhost:8080/debug/pprof/heap > base.out // 當(dāng)前采樣 pprof http://localhost:8080/debug/pprof/heap > current.out // 差異分析 pprof -diff_base=base.out current.out
查看啟動(dòng)以來(lái)的內(nèi)存分配
上面的pprof實(shí)際統(tǒng)計(jì)的是采樣時(shí)刻的內(nèi)存分配情況,我們可以先清空全局內(nèi)存,然后再通過(guò)top查看,會(huì)發(fā)現(xiàn)不再有輸出了:
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%
但我們可以通過(guò)-sample_index=來(lái)控制采樣,對(duì)于內(nèi)存分析來(lái)說(shuō),它有下面幾種取值:
- inuse_space:默認(rèn),表示程序在執(zhí)行時(shí)刻正在使用的內(nèi)存量;
- inuse_objects:程序在執(zhí)行時(shí)刻正在使用的對(duì)象數(shù)量;
- alloc_space:程序自啟動(dòng)以來(lái)分配的總內(nèi)存量,不考慮這些內(nèi)存是否已經(jīng)被釋放;
- alloc_objects:程序自啟動(dòng)以來(lái)分配的總對(duì)象數(shù)量,不考慮這些對(duì)象是否已經(jīng)被釋放;
我們可以查看程序啟動(dòng)以來(lái)的總內(nèi)存分配數(shù)據(jù):
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檢查內(nèi)存泄漏的全過(guò)程的詳細(xì)內(nèi)容,更多關(guān)于Golang pprof內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go?interface{}?轉(zhuǎn)切片類(lèi)型的實(shí)現(xiàn)方法
本文主要介紹了Go?interface{}?轉(zhuǎn)切片類(lèi)型的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02一篇文章讀懂Golang?init函數(shù)執(zhí)行順序
init()函數(shù)會(huì)在包被初始化后自動(dòng)執(zhí)行,并且在main()函數(shù)之前執(zhí)行,但是需要注意的是init()以及main()函數(shù)都是無(wú)法被顯式調(diào)用的,下面這篇文章主要給大家介紹了關(guān)于如何通過(guò)一篇文章讀懂Golang?init函數(shù)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2022-11-11基于Golang實(shí)現(xiàn)Redis分布式鎖解決秒殺問(wèn)題
這篇文章主要給大家介紹了使用Golang實(shí)現(xiàn)Redis分布式鎖解決秒殺問(wèn)題,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-08-08Go語(yǔ)言中validation庫(kù)不能校驗(yàn)零值問(wèn)題的解決方法
在使用 Gin 框架的時(shí)候,前后端傳遞數(shù)據(jù)的時(shí)候,比如使用 JSON 格式,通常會(huì)使用 ShouldBindJSON 去用結(jié)構(gòu)體打 tag 綁定前端傳來(lái)的 JSON 格式數(shù)據(jù),本文給大家介紹了Go語(yǔ)言中validation庫(kù)不能校驗(yàn)零值問(wèn)題的解決方法,需要的朋友可以參考下2024-08-08Golang語(yǔ)言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實(shí)例
這篇文章主要為大家介紹了Golang語(yǔ)言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10