Go slice 內存泄露與垃圾回收
【導讀】本文從一個實際線上問題出發,介紹了由 slice 引發的內存泄漏與其內存分配機制。
最近項目開發中遇到一個問題:在程序中大量使用 golang slice 導致內存佔用暴漲。經過一番分析與定位最終解決了問題,把過程記錄下來與大家分析。
1. 問題現象
-
程序正常運行中突然出現內存佔用飆升,使用 htop 命令查看,程序內存佔用排第一;
-
使用 free -m 命令查看,發現可用內存爲 0;
-
由於程序佔用內存過多,導致 linux 虛擬機響應緩慢卡頓;
-
使用 kill 命令殺掉進程後,內存釋放,虛擬機恢復正常。
2. 查看內存佔用情況
因爲是內存問題,所以首先使用 golang 的 pprof 包進行查看 heap 和 alloc 內存分配詳情。
2.1 引入 pprof 包
//在import中添加pprof包
_ "net/http/pprof"
2.2 打開 pprof 包 web 服務端口
//指定pprof對外提供的http服務的ip和端口,配置爲0.0.0.0表示可以非本機訪問
go func() {
http.ListenAndServe("0.0.0.0:9999", nil)
}()
2.3 程序啓動後查看 pprof 服務端口是否啓用
netstat -antp|grep 9999
tcp6 0 0 :::9989 :::* LISTEN 28294/program
2.4 在網頁上查看 pprof 信息
http://127.0.0.1:9999/debug/pprof/
2.5 更豐富的命令和信息,還是要通過命令行查看
go tool pprof http://127.0.0.1:9999/debug/pprof/heap
連接上後,可以使用 top 命令查看內存使用排行
然後,使用 list 命令直接可以查看到具體是哪一行分配了多少內存
3. 分析問題
由於 pprof 信息顯示,大量內存都是由於在 slice 的 append 方法中分配的。於是又回顧了一下 slice 的原理和坑。
3.1 slice 原理
golang 中 slice 是對數組的引用,底層實現實際上還是數組。對 slice 一定要謹慎使用 append 操作。如果 cap 未變化時,slice 是對數組的引用,並且 append 會修改被引用數組的值。append 操作導致 cap 變化後,會複製被引用的數組,然後切斷引用關係。
3.2 golang 關於 slice 的內存回收
經過分析和查資料發現,網上總結的容易導致內存不能及時回收的情況:
3.2.1 截取長 slice 中的一段導致長 slice 未釋放。
由於底層都是數組,如果截圖長 slice 的一段,其實相當於引用了底層數組中的一小段。只要還有引用,golang 的 gc 就不能回收數組。這種情況導致未使用的數組空間,未及時回收。
解決方案,新建一個長度爲 0 的 slice,將需要的一小段 slice 使用 append 方法添加到新的 slice。再將原來的 slice 置爲 nil。
3.2.2 配合 gc,及時將不再使用的 slice 置爲 nil
如果 slice 中包含很多元素,再只有一小部分元素需要使用的情況下。建議重新分配一個 slice 將需要保留的元素加入其中,將原來的長 slice 整個置爲 nil。
轉自:
zhuanlan.zhihu.com/p/149381458
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/agutEYN4ubmlWJuEqp30TA