對於一個用 python 實現的,長期啟動並執行後台服務進程來說,如果記憶體持續增長,那麼很可能是有了“記憶體泄露”
一、記憶體泄露的原因
對於 python 這種支援記憶體回收的語言來說,怎麼還會有記憶體泄露? 概括來說,有以下三種原因:
1、 所用到的用 C 語言開發的底層模組中出現了記憶體泄露。
2、 代碼中用到了全域的 list、 dict 或其它容器,不停的往這些容器中插入對象,而忘記了在使用完之後進行刪除回收
3、 代碼中有“引用迴圈”, python 垃圾處理機制無法進行回收
二、 記憶體泄露的診斷思路
無論是哪種方式的記憶體泄露,最終表現的形式都是某些 python 對象在不停的增長;因此,首先是要找到這些異常的對象。
三、 記憶體泄露的診斷步驟
用到的工具: gc 模組和 objgraph
objgraph 是一個用於診斷記憶體問題的有用的工具
1、 在服務程式的迴圈邏輯中,選擇出一個診斷點
2、 在診斷點,插入如下診斷語句
import gc<br />import objgraph<br />### 強制進行記憶體回收<br />gc.collect()<br />### 列印出對象數目最多的 50 個類型資訊<br />objgraph.show_most_common_types(limit=50)
3、 檢查統計資訊,找到異常對象。
運行加入診斷語句的服務程式,並將列印到螢幕上的統計資訊重新導向到日誌中。
運行一段時間後,就可以來分析日誌,看看哪些對象在不停的增長。
以我的有問題的程式為例,我將日誌記錄到 log.txt 中,運行一段時間後,發現 tuple 和 list 類型的對象不停增長:
# grep "^list " log.txt
# grep "^tuple " log.txt
由於 tuple 和 list 是一種通用的類型,因此,那些不停增長的 tuple 和 list 到底是什麼對象,還是無法得知。我們還得想點辦法追蹤下去。
如果不停增長的對象,是一些非通用的類型(例如你自己實現的一個 class),那麼問題就比較好定位了。
回到我的這個異常程式,為了找出 tuple 和 list 到底是什嗎? 我採用了排查的方式。由於程式的模組化還不錯,我可以每次禁用一個模組,然後重新跑程式,看看在這種情況下, tuple 和 list 是否仍然不停增長。這樣,很快就能將故障定位到具體的模組中。
最後終於找到了原因,屬於上面總結的第二種原因:
我的程式是一個多線程程式,多個線程作為生產者,一個線程作為消費者,通過非同步隊列通訊。由於消費者的處理速度跟不上生產者的速度,又沒有進行同步, 導致非同步隊列中的對象越來越多。
四、參考文檔