Golang內存泄露場景與定位方式的實現(xiàn)
一、產生原因
Golang有自動垃圾回收機制,但是仍然可能會出現(xiàn)內存泄漏的情況。以下是Golang內存泄漏的常見可能原因:
- 循環(huán)引用:如果兩個或多個對象相互引用,且沒有其他對象引用它們,那么它們就會被垃圾回收機制誤認為是仍在使用的對象,導致內存泄漏。
- 全局變量:在Golang中,全局變量的生命周期與程序的生命周期相同。如果一個全局變量被創(chuàng)建后一直存在于內存中,那么它所占用的內存就無法被回收,可能會導致內存泄漏。
- 未關閉的文件句柄:如果程序打開了文件句柄但沒有關閉它們,那么這些文件句柄所占用的內存就無法被回收,可能會導致內存泄漏。
- 大量的臨時對象:如果程序創(chuàng)建了大量的臨時對象,但沒有及時釋放它們,那么這些對象所占用的內存就無法被回收,可能會導致內存泄漏。
- goroutine泄漏:常見的泄露場景,例如協(xié)程發(fā)生阻塞,Go運行時并不會將處于永久阻塞狀態(tài)的協(xié)程殺掉,因此永久處于阻塞狀態(tài)的協(xié)程所占用的資源將永得不到釋放。
- time.Ticker未關閉導致泄漏:當一個
time.Timer值不再被使用,一段時間后它將被自動垃圾回收掉。 但對于一個不再使用的time.Ticker值,我們必須調用它的Stop方法結束它,否則它將永遠不會得到回收。
二、排查方式
如果出現(xiàn)內存泄漏,可以使用以下方式進行分析,找出內存泄漏的原因并進行修復。
- 使用 Go 語言自帶的 pprof 工具進行分析。pprof 可以生成程序的 CPU 和內存使用情況的報告,幫助開發(fā)者找出程序中的性能瓶頸和內存泄漏問題??梢酝ㄟ^在代碼中添加
import _ "net/http/pprof"和http.ListenAndServe("localhost:6060", nil)來開啟 pprof 工具。 - 使用 Golang 內置的
runtime包進行分析。runtime包提供了一些函數(shù),包括SetFinalizer、ReadMemStats和Stack等,可以幫助開發(fā)者了解程序的內存使用情況和內存泄漏問題。 - 使用第三方工具進行分析。例如,可以使用
go-torch工具生成火焰圖,幫助開發(fā)者找出程序中的性能瓶頸和內存泄漏問題。 - 使用
go vet工具進行靜態(tài)分析。go vet可以檢查程序中的常見錯誤和潛在問題,包括內存泄漏問題。 - 代碼審查。開發(fā)者可以通過代碼審查來找出程序中的潛在問題和內存泄漏問題。
三、通過 pprof 的命令排查內存泄露問題
3.1 通過 pprof 的命令行分析 heap
命令行執(zhí)行命令: go tool pprof -inuse_space [<http://127.0.0.1:9999/debug/pprof/heap>](<http://spark-master.x.upyun.com/debug/pprof/heap>)
這個命令的作用是, 抓取當前程序已使用的 heap. 抓取后, 就可以進行類似于 gdb 的交互操作.
top 命令, 默認能列出當前程序中內存占用排名前 10 的函數(shù). 如圖. 當時進行到這一步的時候, 我就非常驚訝, 因為 time.NewTimer 居然占據(jù)了 6 個多 G 的內存.

list <函數(shù)名>, 展現(xiàn)函數(shù)內部的內存占用. 使用 list time.NewTimer 查看了該函數(shù)的內部, 真相大白了, 原來每次調用 NewTimer 都會創(chuàng)建一個 channel, 還會生成一個結構體 runtimeTimer, 應該就是這兩個地方內存沒有釋放造成的內存泄露.

3.2 修改 for ... select ... time.After 造成的內存泄露
原來程序中存在如下代碼:
for {
select {
case a := <-chanA:
...
case b := <-chanB:
....
case <-time.After(20*time.Minutes):
return nil, errors.New("download timeout")
}
time.After 就是封裝了一層的 NewTimer, time.After 的源碼:
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
修復該錯誤, 只調用一次 NewTimer:
downloadTimeout := time.NewTimer(20 * time.Minute)
// 添加關閉時退出操作
defer downloadTimeout.Stop()
for {
select {
case a := <-chanA:
...
case b := <-chanB:
....
case <-downloadTimeout.C:
return nil, errors.New("download timeout")
}
四、總結
通過這篇文章我們了解到Golang內存泄漏的常見可能原因有哪些:
- 循環(huán)引用:如果兩個或多個對象相互引用,且沒有其他對象引用它們,那么它們就會被垃圾回收機制誤認為是仍在使用的對象,導致內存泄漏。
- 全局變量:在Golang中,全局變量的生命周期與程序的生命周期相同。如果一個全局變量被創(chuàng)建后一直存在于內存中,那么它所占用的內存就無法被回收,可能會導致內存泄漏。
- 未關閉的文件句柄:如果程序打開了文件句柄但沒有關閉它們,那么這些文件句柄所占用的內存就無法被回收,可能會導致內存泄漏。
- 大量的臨時對象:如果程序創(chuàng)建了大量的臨時對象,但沒有及時釋放它們,那么這些對象所占用的內存就無法被回收,可能會導致內存泄漏。
- goroutine泄漏:比較常見的泄露場景,例如協(xié)程發(fā)生阻塞,Go運行時并不會將處于永久阻塞狀態(tài)的協(xié)程殺掉,因此永久處于阻塞狀態(tài)的協(xié)程所占用的資源將永得不到釋放。
- time.Ticker未關閉導致泄漏:當一個
time.Timer值不再被使用,一段時間后它將被自動垃圾回收掉。 但對于一個不再使用的time.Ticker值,我們必須調用它的Stop方法結束它,否則它將永遠不會得到回收。
然后介紹了相關排查工具以及pprof如何排查內存泄露問題。
五、參考鏈接
到此這篇關于Golang內存泄露場景與定位方式的實現(xiàn)的文章就介紹到這了,更多相關Golang內存泄露內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go實現(xiàn)整合Logrus實現(xiàn)日志打印
這篇文章主要介紹了Go實現(xiàn)整合Logrus實現(xiàn)日志打印,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07
go語言中切片Slice與數(shù)組Array對比以及panic:?runtime?error:?index?out?
go語言中數(shù)組與其他語言有在顯著的不同,包括其不能夠進行添加,以及值拷貝的特性,下面這篇文章主要給大家介紹了關于go語言中切片Slice與數(shù)組Array對比以及panic:?runtime?error:?index?out?of?range問題解決的相關資料,需要的朋友可以參考下2022-07-07

