python 記憶體洩露的診斷

iteye_3653發表於2010-11-29
對於一個用 python 實現的,長期執行的後臺服務程式來說,如果記憶體持續增長,那麼很可能是有了“記憶體洩露”。
最近在我的專案中,就出現了記憶體持續增長的情況,goolge 了一下,發現 [url=http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks]Tracing Python memory leaks[/url] 講了一種診斷方式,並給出了例項。而我的案例與此文稍有不同,下面就結合我的案例,談談如何診斷記憶體洩露:

一、記憶體洩露的原因
對於 python 這種支援垃圾回收的語言來說,怎麼還會有記憶體洩露? 概括來說,有以下三種原因:

1、 所用到的用 C 語言開發的底層模組中出現了記憶體洩露。
2、 程式碼中用到了全域性的 list、 dict 或其它容器,不停的往這些容器中插入物件,而忘記了在使用完之後進行刪除回收
3、 程式碼中有“引用迴圈”, python 垃圾處理機制無法進行回收


二、 記憶體洩露的診斷思路

無論是哪種方式的記憶體洩露,最終表現的形式都是某些 python 物件在不停的增長;因此,首先是要找到這些異常的物件。

三、 記憶體洩露的診斷步驟

用到的工具: gc 模組和 [url=http://mg.pov.lt/objgraph.py]objgraph[/url] 模組
objgraph 是一個用於診斷記憶體問題的有用的工具


1、 在服務程式的迴圈邏輯中,選擇出一個診斷點


2、 在診斷點,插入如下診斷語句


import gc
import objgraph
### 強制進行垃圾回收
gc.collect()
### 列印出物件數目最多的 50 個型別資訊
objgraph.show_most_common_types(limit=50)



3、 檢查統計資訊,找到異常物件。

執行加入診斷語句的服務程式,並將列印到螢幕上的統計資訊重定向到日誌中。
執行一段時間後,就可以來分析日誌,看看哪些物件在不停的增長。

以我的程式為例,我將日誌記錄到 log.txt 中,執行一段時間後,發現 tuple 和 list 型別的物件不停增長:


# grep "^list " log.txt
# grep "^tuple " log.txt


如果不停增長的物件,是一些非通用的型別(例如你自己實現的一個 class),那麼問題就比較好定位,例如 [url=http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks]Tracing Python memory leaks[/url]中提到的案例。

而對 tuple 和 list 這類通用型別,要想知道物件到底是什麼,洩露發生在哪裡,還得想點辦法。我採用了排查的方式。由於程式的模組化還不錯,可以每次禁用一個模組,然後重新跑程式,重新檢查日誌,看看 tuple 和 list 是否仍然不停增長。這樣,很快就能將故障定位到具體的模組中。

最後終於找到了原因,屬於上面總結的第二種原因:
我的程式是一個多執行緒程式,多個執行緒作為生產者,一個執行緒作為消費者,通過將一個 tuple 物件送入非同步佇列進行通訊。由於消費者的處理速度跟不上生產者的速度,又沒有進行同步, 導致非同步佇列中的物件越來越多。

四、參考文件
1. [url]http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks[/url]
2. [url]http://mg.pov.lt/blog/python-object-graphs.html[/url]
3. [url]http://mg.pov.lt/blog/hunting-python-memleaks[/url]

相關文章