Golang使用pprof檢查內(nèi)存泄漏的全過程
前言
pprof 是golang提供的一款分析工具,可以分析CPU,內(nèi)存的使用情況,本篇文章關(guān)注它在分析內(nèi)存泄漏方面的應(yīng)用。pprof 不能直觀顯示出某個函數(shù)發(fā)生了泄漏,它的用途是列出當前內(nèi)存分配較大的位置,通過比較一段時間pprof的結(jié)果,可以發(fā)現(xiàn)內(nèi)存變化。
測試代碼
比如我們有下面的函數(shù),在請求中對全局變量分配了一塊內(nè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))
// 賦值給全局變量,但沒有回收
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工具,啟動服務(wù)后,內(nèi)存分析結(jié)果保存在http://localhost:8080/debug/pprof/heap。
發(fā)送請求
我們往/leaky-endpoint發(fā)送一些請求,觸發(fā)全局內(nèi)存分配,可以使用 ab 命令:
ab -n 1000 -c 10 http://localhost:8080/leaky-endpoint
-n:請求數(shù)量,我們總共發(fā)送1000個請求
-c:并發(fā)數(shù)量,1000個請求通過10個并發(fā)線程發(fā)送
分析內(nèi)存
查看內(nèi)存分配
我們可以通過下面的命令查看分析結(jié)果:
pprof http://localhost:8080/debug/pprof/heap // output: // Entering interactive mode (type "help" for commands, "o" for options) // (pprof)
輸入上述命令后,會進入命令行交互模式,我們可以輸入一系列幫助命令查看內(nèi)存分析結(jié)果:
- top:輸出內(nèi)存分配最多的幾個函數(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值占總消耗資源的百分比,這個百分比是基于整個程序在采樣期間的資源消耗來計算的; - sum%: 累積百分比,表示從輸出列表頂部開始到當前行為止的所有函數(shù)的
flat百分比之和。這可以幫助你理解最頂部的函數(shù)累積起來消耗了多少資源; - cum: 函數(shù)自身
flat,加上它調(diào)用的所有函數(shù)在采樣期間消耗的內(nèi)存; - cum%:
cum值占總消耗資源的百分比。這個百分比顯示了函數(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:在瀏覽器中生成并打開 SVG 格式的調(diào)用圖
png:生成 PNG 格式的調(diào)用圖

比較內(nèi)存分配
為了分析內(nèi)存泄漏,我們往往需要統(tǒng)計一段時間開始和結(jié)束時的內(nèi)存變化情況,在上面的基礎(chǔ)上,我們可以再用ab命令觸發(fā)幾次內(nèi)存分配,然后通過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)存較之前增長了,實際上我們可以通過 -diff_base 來比較兩次采樣結(jié)果的差異:
// 第一次采樣 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
查看啟動以來的內(nèi)存分配
上面的pprof實際統(tǒng)計的是采樣時刻的內(nèi)存分配情況,我們可以先清空全局內(nèi)存,然后再通過top查看,會發(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%
但我們可以通過-sample_index=來控制采樣,對于內(nèi)存分析來說,它有下面幾種取值:
- inuse_space:默認,表示程序在執(zhí)行時刻正在使用的內(nèi)存量;
- inuse_objects:程序在執(zhí)行時刻正在使用的對象數(shù)量;
- alloc_space:程序自啟動以來分配的總內(nèi)存量,不考慮這些內(nèi)存是否已經(jīng)被釋放;
- alloc_objects:程序自啟動以來分配的總對象數(shù)量,不考慮這些對象是否已經(jīng)被釋放;
我們可以查看程序啟動以來的總內(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)存泄漏的全過程的詳細內(nèi)容,更多關(guān)于Golang pprof內(nèi)存泄漏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go?interface{}?轉(zhuǎn)切片類型的實現(xiàn)方法
本文主要介紹了Go?interface{}?轉(zhuǎn)切片類型的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
一篇文章讀懂Golang?init函數(shù)執(zhí)行順序
init()函數(shù)會在包被初始化后自動執(zhí)行,并且在main()函數(shù)之前執(zhí)行,但是需要注意的是init()以及main()函數(shù)都是無法被顯式調(diào)用的,下面這篇文章主要給大家介紹了關(guān)于如何通過一篇文章讀懂Golang?init函數(shù)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2022-11-11
基于Golang實現(xiàn)Redis分布式鎖解決秒殺問題
這篇文章主要給大家介紹了使用Golang實現(xiàn)Redis分布式鎖解決秒殺問題,文中有詳細的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下2023-08-08
Golang語言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實例
這篇文章主要為大家介紹了Golang語言使用像JAVA?Spring注解一樣的DI和AOP依賴注入實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10

