Python內存泄露的診斷


對於一個用 python 實現的,長期運行的後臺服務進程來說,如果內存持續增長,那麼很可能是有了內存方面的問題。 在我曾經的一個項目中,就出現了這種內存持續增長的情況,goolge 一下,發現 Tracing Python memory leaks 講了一種診斷方式,並給出了實例。

而我的案例與此文稍有不同,下面就結合我的案例,談談如何診斷這種“內存泄露”的問題。

內存泄露的原因

對於 python 這種支持垃圾回收的語言來說,怎麼還會有內存泄露? 概括來說,有以下三種原因:

  • 所用到的用C 語言開發的底層模塊中出現了內存泄露。
  • 代碼中用到了全局的 list、 dict 或其它容器,不停的往這些容器中插入對象,而忘記了在使用完之後進行刪除回收
  • 代碼中有引用循環,python 垃圾處理機制無法進行回收

內存泄露的診斷思路

無論是哪種方式的內存泄露,最終表現的形式都是某些 python 對象在不停的增長;因此,首先是要找到這些異常的對象。

內存泄露診斷用到的工具

  • gc 模塊
  • objgraph模塊
    • objgraph 是一個用於診斷內存問題的有用的工具

內存泄露診斷的步驟

  • 在服務程序的循環邏輯中,選擇出一個診斷點
  • 在診斷點,插入如下診斷語句
Python代碼
   1 import gc
   2 import objgraph
   3 gc.collect()
   4 ### 強制進行垃圾回收
   5 objgraph.show_most_common_types(limit=50)
   6 ### 打印出對象數目最多的 50 個類型信息 
 

  • 檢查統計信息,找到異常對象。
  • 運行加入診斷語句的服務程序,並將打印到屏幕上的統計信息重定向到日誌中。
  • 運行一段時間後,就可以來shell腳本來分析日誌,看看哪些對象在不停的增長。
    • 以我的程序爲例,我將日誌記錄到 log.txt 中,運行一段時間後,發現 tuple 和 list 類型的對象不停增長:
Shell 代碼
   1 # grep "^list " log.txt
   2 # grep "^tuple " log.txt 
 

如果不停增長的對象,是一些非通用的類型(例如你自己實現的一個 class),那麼問題就比較好定位,例如Tracing Python memory leaks中提到的案例。 

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

最後終於找到了原因,屬於上面總結的第二種原因: 
我的程序是一個多線程程序,多個線程作爲生產者,一個線程作爲消費者,通過將一個 tuple 對象送入異步隊列進行通信。由於消費者的處理速度跟不上生產者的速度,又沒有進行同步, 導致異步隊列中的對象越來越多。

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章