Go slice 內存泄露與垃圾回收

【導讀】本文從一個實際線上問題出發,介紹了由 slice 引發的內存泄漏與其內存分配機制。

最近項目開發中遇到一個問題:在程序中大量使用 golang slice 導致內存佔用暴漲。經過一番分析與定位最終解決了問題,把過程記錄下來與大家分析。

1. 問題現象

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