Golang內(nèi)存泄露場(chǎng)景與定位方式的實(shí)現(xiàn)
一、產(chǎn)生原因
Golang有自動(dòng)垃圾回收機(jī)制,但是仍然可能會(huì)出現(xiàn)內(nèi)存泄漏的情況。以下是Golang內(nèi)存泄漏的常見可能原因:
- 循環(huán)引用:如果兩個(gè)或多個(gè)對(duì)象相互引用,且沒有其他對(duì)象引用它們,那么它們就會(huì)被垃圾回收機(jī)制誤認(rèn)為是仍在使用的對(duì)象,導(dǎo)致內(nèi)存泄漏。
- 全局變量:在Golang中,全局變量的生命周期與程序的生命周期相同。如果一個(gè)全局變量被創(chuàng)建后一直存在于內(nèi)存中,那么它所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- 未關(guān)閉的文件句柄:如果程序打開了文件句柄但沒有關(guān)閉它們,那么這些文件句柄所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- 大量的臨時(shí)對(duì)象:如果程序創(chuàng)建了大量的臨時(shí)對(duì)象,但沒有及時(shí)釋放它們,那么這些對(duì)象所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- goroutine泄漏:常見的泄露場(chǎng)景,例如協(xié)程發(fā)生阻塞,Go運(yùn)行時(shí)并不會(huì)將處于永久阻塞狀態(tài)的協(xié)程殺掉,因此永久處于阻塞狀態(tài)的協(xié)程所占用的資源將永得不到釋放。
- time.Ticker未關(guān)閉導(dǎo)致泄漏:當(dāng)一個(gè)
time.Timer
值不再被使用,一段時(shí)間后它將被自動(dòng)垃圾回收掉。 但對(duì)于一個(gè)不再使用的time.Ticker
值,我們必須調(diào)用它的Stop
方法結(jié)束它,否則它將永遠(yuǎn)不會(huì)得到回收。
二、排查方式
如果出現(xiàn)內(nèi)存泄漏,可以使用以下方式進(jìn)行分析,找出內(nèi)存泄漏的原因并進(jìn)行修復(fù)。
- 使用 Go 語(yǔ)言自帶的 pprof 工具進(jìn)行分析。pprof 可以生成程序的 CPU 和內(nèi)存使用情況的報(bào)告,幫助開發(fā)者找出程序中的性能瓶頸和內(nèi)存泄漏問題。可以通過(guò)在代碼中添加
import _ "net/http/pprof"
和http.ListenAndServe("localhost:6060", nil)
來(lái)開啟 pprof 工具。 - 使用 Golang 內(nèi)置的
runtime
包進(jìn)行分析。runtime
包提供了一些函數(shù),包括SetFinalizer
、ReadMemStats
和Stack
等,可以幫助開發(fā)者了解程序的內(nèi)存使用情況和內(nèi)存泄漏問題。 - 使用第三方工具進(jìn)行分析。例如,可以使用
go-torch
工具生成火焰圖,幫助開發(fā)者找出程序中的性能瓶頸和內(nèi)存泄漏問題。 - 使用
go vet
工具進(jìn)行靜態(tài)分析。go vet
可以檢查程序中的常見錯(cuò)誤和潛在問題,包括內(nèi)存泄漏問題。 - 代碼審查。開發(fā)者可以通過(guò)代碼審查來(lái)找出程序中的潛在問題和內(nèi)存泄漏問題。
三、通過(guò) pprof 的命令排查內(nèi)存泄露問題
3.1 通過(guò) 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>)
這個(gè)命令的作用是, 抓取當(dāng)前程序已使用的 heap. 抓取后, 就可以進(jìn)行類似于 gdb 的交互操作.
top 命令, 默認(rèn)能列出當(dāng)前程序中內(nèi)存占用排名前 10 的函數(shù). 如圖. 當(dāng)時(shí)進(jìn)行到這一步的時(shí)候, 我就非常驚訝, 因?yàn)?nbsp;time.NewTimer
居然占據(jù)了 6 個(gè)多 G 的內(nèi)存.
list <函數(shù)名>
, 展現(xiàn)函數(shù)內(nèi)部的內(nèi)存占用. 使用 list time.NewTimer
查看了該函數(shù)的內(nèi)部, 真相大白了, 原來(lái)每次調(diào)用 NewTimer
都會(huì)創(chuàng)建一個(gè) channel, 還會(huì)生成一個(gè)結(jié)構(gòu)體 runtimeTimer
, 應(yīng)該就是這兩個(gè)地方內(nèi)存沒有釋放造成的內(nèi)存泄露.
3.2 修改 for ... select ... time.After 造成的內(nèi)存泄露
原來(lái)程序中存在如下代碼:
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 }
修復(fù)該錯(cuò)誤, 只調(diào)用一次 NewTimer
:
downloadTimeout := time.NewTimer(20 * time.Minute) // 添加關(guān)閉時(shí)退出操作 defer downloadTimeout.Stop() for { select { case a := <-chanA: ... case b := <-chanB: .... case <-downloadTimeout.C: return nil, errors.New("download timeout") }
四、總結(jié)
通過(guò)這篇文章我們了解到Golang內(nèi)存泄漏的常見可能原因有哪些:
- 循環(huán)引用:如果兩個(gè)或多個(gè)對(duì)象相互引用,且沒有其他對(duì)象引用它們,那么它們就會(huì)被垃圾回收機(jī)制誤認(rèn)為是仍在使用的對(duì)象,導(dǎo)致內(nèi)存泄漏。
- 全局變量:在Golang中,全局變量的生命周期與程序的生命周期相同。如果一個(gè)全局變量被創(chuàng)建后一直存在于內(nèi)存中,那么它所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- 未關(guān)閉的文件句柄:如果程序打開了文件句柄但沒有關(guān)閉它們,那么這些文件句柄所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- 大量的臨時(shí)對(duì)象:如果程序創(chuàng)建了大量的臨時(shí)對(duì)象,但沒有及時(shí)釋放它們,那么這些對(duì)象所占用的內(nèi)存就無(wú)法被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏。
- goroutine泄漏:比較常見的泄露場(chǎng)景,例如協(xié)程發(fā)生阻塞,Go運(yùn)行時(shí)并不會(huì)將處于永久阻塞狀態(tài)的協(xié)程殺掉,因此永久處于阻塞狀態(tài)的協(xié)程所占用的資源將永得不到釋放。
- time.Ticker未關(guān)閉導(dǎo)致泄漏:當(dāng)一個(gè)
time.Timer
值不再被使用,一段時(shí)間后它將被自動(dòng)垃圾回收掉。 但對(duì)于一個(gè)不再使用的time.Ticker
值,我們必須調(diào)用它的Stop
方法結(jié)束它,否則它將永遠(yuǎn)不會(huì)得到回收。
然后介紹了相關(guān)排查工具以及pprof如何排查內(nèi)存泄露問題。
五、參考鏈接
2.使用 pprof 排查 Golang 內(nèi)存泄露
到此這篇關(guān)于Golang內(nèi)存泄露場(chǎng)景與定位方式的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang內(nèi)存泄露內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
云端golang開發(fā),無(wú)需本地配置,能上網(wǎng)就能開發(fā)和運(yùn)行
這篇文章主要介紹了云端golang開發(fā),無(wú)需本地配置,能上網(wǎng)就能開發(fā)和運(yùn)行的相關(guān)資料,需要的朋友可以參考下2023-10-10Golang使用泛型對(duì)數(shù)組進(jìn)行去重的實(shí)現(xiàn)
本文主要介紹了Golang使用泛型對(duì)數(shù)組進(jìn)行去重的實(shí)現(xiàn),通過(guò)使用類型參數(shù)T和類型約束any,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02Go語(yǔ)言操作Excel利器之excelize類庫(kù)詳解
Excelize是Go語(yǔ)言編寫的用于操作Office Excel文檔基礎(chǔ)庫(kù),基于ECMA-376,ISO/IEC 29500國(guó)際標(biāo)準(zhǔn),可以使用它來(lái)讀取、寫入由Excel 2007及以上版本創(chuàng)建的電子表格文檔,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言操作Excel利器之excelize類庫(kù)的相關(guān)資料,需要的朋友可以參考下2022-10-10golang中單機(jī)鎖的具體實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了golang中單機(jī)鎖的具體實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03Go語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)例詳解
鏈表由一系列結(jié)點(diǎn)(鏈表中每一個(gè)元素稱為結(jié)點(diǎn))組成,結(jié)點(diǎn)可以在運(yùn)行時(shí)動(dòng)態(tài)生成。本文將通過(guò)五個(gè)例題帶大家深入了解Go語(yǔ)言中單鏈表的用法,感興趣的可以了解一下2022-08-08golang?chan傳遞數(shù)據(jù)的性能開銷詳解
這篇文章主要為大家詳細(xì)介紹了Golang中chan在接收和發(fā)送數(shù)據(jù)時(shí)因?yàn)椤皬?fù)制”而產(chǎn)生的開銷,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-01-01Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例
這篇文章主要為大家介紹了Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫(kù)的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫(kù),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下2023-03-03golang進(jìn)行簡(jiǎn)單權(quán)限認(rèn)證的實(shí)現(xiàn)
本文主要介紹了golang簡(jiǎn)單權(quán)限認(rèn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09