Pprof定位Go程式記憶體洩露
Golang 為我們提供了 pprof 工具。掌握之後,可以幫助排查程式的記憶體洩露問題,當然除了排查記憶體,它也能排查 CPU 佔用過高,執行緒死鎖的這些問題,不過這篇文章我們會聚焦在怎麼用 pprof 排查程式的記憶體洩露問題。
比如查資料庫時,有個查詢條件在一定情況下應用不到,導致程式被迫持有一個超大的結果集,這樣持續一段時間,執行相同任務的執行緒一多,就會造成記憶體洩露。
pprof工具集,提供了Go程式內部多種效能指標的取樣能力,我們常會用到的效能取樣指標有這些:
- profile:CPU取樣
- heap:堆中活躍物件的記憶體分配情況的取樣
- goroutine:當前所有goroutine的堆疊資訊
- allocs: 會取樣自程式啟動所有物件的記憶體分配資訊(包括已經被GC回收的記憶體)
- threadcreate:取樣導致建立新系統執行緒的堆疊資訊
上面 heap 和 allocs 是兩個與記憶體相關的指標, allocs 指標會取樣自程式啟動所有物件的記憶體分配資訊。一般是在想要分析哪些程式碼能最佳化提高效率時,檢視的指標。針對檢視記憶體洩露問題的分析,使用則的是 heap 指標裡的取樣資訊。
pprof 的 heap 資訊,是對堆中活躍物件的記憶體分配情況的取樣。Go 裡邊哪些物件會被分配到堆上?一般概況就是,被多個函式引用的物件、全域性變數、超過一定體積(32KB)的物件都會被分配到堆上,當然對於 Go 來說還會有其他的一些情況會讓物件逃逸到堆上。
具體哪些變數會被分配到堆上、以及記憶體逃逸的事兒,就不多說了,想看詳細情況的,看下面這兩篇文章。
- 圖解Go記憶體管理器的記憶體分配策略
- Go記憶體管理之程式碼的逃逸分析
要使用 pprof 獲取 heap 指標的取樣資訊,一種情況是使用 "net/http/pprof" 包
import ( "net/http/pprof" ) func main() { http.HandleFunc("/debug/pprof/heap", pprof.Index) ...... http.ListenAndServe(":80", nil) }
然後透過 HTTP 請求的方式獲得
curl -sK -v
還有一種主要的方法是使用runtime.pprof 提供的方法,把取樣資訊儲存到檔案。
pprof.Lookup("heap").WriteTo(profile_file, 0)
關於這兩個包的使用方式,以及怎麼把資訊取樣到檔案,上面介紹自動取樣的文章裡有詳細的介紹,這裡就不再花過多篇幅了。
下面進入文章的正題, 拿到取樣檔案後,怎麼用 pprof 排查出程式碼哪裡導致了記憶體洩露。
pprof 在取樣 heap 指標的資訊時,使用的是 runtime.MemProfile 函式,該函式預設收集每個 512KB 已分配位元組的分配資訊。我們可以設定讓 runtime.MemProfile 收集所有物件的資訊,不過這會對程式的效能造成影響。
當我們拿到取樣檔案後,就可以透過 go tool pprof 將資訊載入到一個互動模式的控制檯中。
> go tool pprof heap.out
進入,互動式控制檯後,一般會有如下的提示:
File: heap.out Type: inuse_space Time: Feb 1, 2022 at 10:11am (CST) Entering interactive mode (type "help" for commands, "o" for options)
這裡的 Type: inuse_space 指明瞭檔案內取樣資訊的型別, Type 可能的值有:
- inuse_space — 已分配但尚未釋放的記憶體空間
- inuse_objects——已分配但尚未釋放的物件數量
- alloc_space — 分配的記憶體總量(已釋放的也會統計)
- alloc_objects — 分配的物件總數(無論是否釋放)
接下來,介紹一個 pprof 互動式模式下的 top,也可以是 topN,比如 top10。這個跟 系統的 top 類似,輸出 Top N 個最佔用記憶體的函式。
(pprof) top10 Showing nodes accounting for 134.55MB, 92.16% of 145.99MB total Dropped 60 nodes (cum <= 0.73MB) Showing top 10 nodes out of 117 flat flat% sum% cum cum% 60.53MB 41.46% 41.46% 85.68MB 58.69% github.com/jinzhu/gorm.glob..func2 18.65MB 12.77% 54.24% 18.65MB 12.77% regexp.(*Regexp).Split 16.95MB 11.61% 65.84% 16.95MB 11.61% github.com/jinzhu/gorm.(*Scope).AddToVars 8.67MB 5.94% 71.78% 129.05MB 88.39% example.com/xxservice/dummy.GetLargeData 7.50MB 5.14% 82.63% 7.50MB 5.14% reflect.packEface 6.50MB 4.45% 87.08% 6.50MB 4.45% fmt.Sprintf 4MB 2.74% 89.82% 4MB 2.74% runtime.malg 1.91MB 1.31% 91.13% 1.91MB 1.31% strings.Replace 1.51MB 1.03% 92.16% 1.51MB 1.03% bytes.makeSlice
在這兩個裡邊,最佔用記憶體的前三是 gorm 庫的一個方法,gorm 是個 ORM 庫,但是導致它記憶體洩露的原因應該是後面一個有業務邏輯的程式碼,dummy.GetLargeData 方法。
在 top 指令輸出的列表中,我們可以看到兩個值,flat 和 cum。
- flat:表示此函式分配、並由該函式持有的記憶體空間。
- cum:表示由這個函式或它呼叫堆疊下面的函式分配的記憶體總量。
此外 sum % 表示前面幾行輸出的 flat百分比之和, 比如上面第四行 sum% 列的值是, 71.78% 實際上就是它以及它上面三行輸出的 flat% 的總和。
定位到導致記憶體洩露的函式後,後面要做的最佳化問題就是,深入函式內部,看哪裡使用不當或者有邏輯上的疏忽,比如我開頭舉得那個查詢條件在有些情況下應用不上的例子。
當然如果你想在函式內部再精確的定位到底是哪段程式碼導致的記憶體溢位,也是有辦法的,這時候就需要用到 list 指令了。
list 指令可以列出函式內部,每一行程式碼執行時分配的記憶體(如果分析CPU的取樣檔案,則會顯示CPU使用時間)
(pprof) list dummy.GetLargeData Total: 814.62MB ROUTINE ======================== dummy.GetLargeData in /home/xxx/xxx/xxx.go 814.62MB 814.62MB (flat, cum) 100% of Total . . 20: }() . . 21: . . 22: tick := time.Tick(time.Second / 100) . . 23: var buf []byte . . 24: for range tick { 814.62MB 814.62MB 25: buf = append(buf, make([]byte, 1024*1024)...) . . 26: } . . 27:} . . 28:
這裡把用 pprof 怎麼排查程式的記憶體洩露做了個簡單的總結,當然如果你們公司有條件上持續取樣,或者我之前文章說的自動取樣方案的話,最好還是用上,讓機器幫我們做這些事情。
不過不管是用什麼辦法,最終只能是幫我們定位出來哪裡造成了記憶體洩露,至於要怎麼最佳化解決這個問題,還得具體情況具體分析,如果是一些業務邏輯實現上的問題,那就得跟團隊商量一下實現方式,可能還會涉及到產品上的一些改動。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2855864/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 記一次Go websocket 專案記憶體洩露排查 + 使用Go pprof定位記憶體洩露GoWeb記憶體洩露
- 線上記憶體洩露定位--memleak工具記憶體洩露
- 如何定位和解決記憶體洩露記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- 記憶體洩露記憶體洩露
- js記憶體洩露JS記憶體洩露
- JavaScript記憶體洩露JavaScript記憶體洩露
- 記憶體洩露嗎記憶體洩露
- SHBrowseForFolder 記憶體洩露記憶體洩露
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- Lowmemorykiller記憶體洩露分析記憶體洩露
- 定位並修復 Go 中的記憶體洩漏Go記憶體
- 記憶體分析與記憶體洩漏定位記憶體
- C程式記憶體洩露檢測工具——ValgrindC程式記憶體洩露
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- js記憶體洩露的原因JS記憶體洩露
- Java記憶體洩露的原因Java記憶體洩露
- JAVA 記憶體洩露的理解Java記憶體洩露
- IE中的記憶體洩露記憶體洩露
- 學習Java:記憶體洩露Java記憶體洩露
- PHP 記憶體洩漏分析定位PHP記憶體
- 用 TDengine 3.0 碰到“記憶體洩露”?定位問題原因很關鍵記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- Android 檢測記憶體洩露Android記憶體洩露
- 如何處理 JavaScript 記憶體洩露JavaScript記憶體洩露
- leaks工具查詢記憶體洩露記憶體洩露
- 記憶體洩露引起的問題記憶體洩露
- MFC記憶體洩露與檢測記憶體洩露
- JavaScript中的記憶體洩露模式JavaScript記憶體洩露模式
- ThreaLocal記憶體洩露的問題記憶體洩露
- JVM與記憶體洩露問題JVM記憶體洩露
- 使用多年的 go pprof 檢查記憶體洩漏的方法居然是錯的?!Go記憶體
- 記憶體洩漏-原因、避免和定位記憶體
- 如何避免JavaScript的記憶體洩露及記憶體管理技巧JavaScript記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- win10驅動記憶體洩露如何解決_win10記憶體洩露處理方法Win10記憶體洩露
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露