Pprof定位Go程式記憶體洩露

roc_guo發表於2022-02-16

Pprof定位Go程式記憶體洩露Pprof定位Go程式記憶體洩露

Golang 為我們提供了 pprof 工具。掌握之後,可以幫助排查程式的記憶體洩露問題,當然除了排查記憶體,它也能排查 CPU 佔用過高,執行緒死鎖的這些問題,不過這篇文章我們會聚焦在怎麼用 pprof 排查程式的記憶體洩露問題。

比如查資料庫時,有個查詢條件在一定情況下應用不到,導致程式被迫持有一個超大的結果集,這樣持續一段時間,執行相同任務的執行緒一多,就會造成記憶體洩露。

記憶體洩露指標

pprof工具集,提供了Go程式內部多種效能指標的取樣能力,我們常會用到的效能取樣指標有這些:

  1. profile:CPU取樣
  1. heap:堆中活躍物件的記憶體分配情況的取樣
  1. goroutine:當前所有goroutine的堆疊資訊
  1. allocs: 會取樣自程式啟動所有物件的記憶體分配資訊(包括已經被GC回收的記憶體)
  1. threadcreate:取樣導致建立新系統執行緒的堆疊資訊

上面 heap 和 allocs 是兩個與記憶體相關的指標, allocs 指標會取樣自程式啟動所有物件的記憶體分配資訊。一般是在想要分析哪些程式碼能最佳化提高效率時,檢視的指標。針對檢視記憶體洩露問題的分析,使用則的是 heap 指標裡的取樣資訊。

Heap

pprof 的 heap 資訊,是對堆中活躍物件的記憶體分配情況的取樣。Go 裡邊哪些物件會被分配到堆上?一般概況就是,被多個函式引用的物件、全域性變數、超過一定體積(32KB)的物件都會被分配到堆上,當然對於 Go 來說還會有其他的一些情況會讓物件逃逸到堆上。

具體哪些變數會被分配到堆上、以及記憶體逃逸的事兒,就不多說了,想看詳細情況的,看下面這兩篇文章。

  1. 圖解Go記憶體管理器的記憶體分配策略
  1. Go記憶體管理之程式碼的逃逸分析
Heap 取樣

要使用 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尋找記憶體洩露

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 可能的值有:

  1. inuse_space — 已分配但尚未釋放的記憶體空間
  1. inuse_objects——已分配但尚未釋放的物件數量
  1. alloc_space — 分配的記憶體總量(已釋放的也會統計)
  1. 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。

  1. flat:表示此函式分配、並由該函式持有的記憶體空間。
  1. 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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章