[Python] 使用 pympler 找出參考物件,讓記憶體無法釋放的元兇

[Python] 使用 pympler 找出參考物件,讓記憶體無法釋放的元兇

昨天 使用 pympler 偵測記憶體洩露 (memory leak)

發現有一些我們的物件持續增長,

因此去程式裡追相關物件被產生的地點、物件被放到哪去、

以及物件裡又包含了什麼東西,

因為 循環參考 (cyclic reference) 很有可能就是這樣產生的。

 

一個比較簡單的循環參考可能像這樣:

a = A()
b = A()

a.data = b
b.data = a

 

上例中,a 參考到 b,而 b 也參考到 a,

這就導致 a 和 b 的參考計數 (reference count) 無法降為 0,

因此 a 和 b 都無法被垃圾回收 (garbage collection) 機制清除掉。

 

當然上述的例子很假,就算這麼寫,應該也很快就能抓出問題。

但實際的程式的循環參考可能是很隱密的,難以肉眼察覺。

這時我們可以利用 pympler.muppy 模組來幫忙,把「某個物件被誰參考到」都列出來,

這樣就更容易定位問題了~

 

舉例來說,在 使用 pympler 偵測記憶體洩露 (memory leak) 那篇裡面,

我們發現 CurlWrapper 這個型別的物件持續產生,沒被釋放掉。

因此,我讓程式在開始出現記憶體洩露 (memory leak) 後,

去執行下面這段程式:

def mem_obj_referrer(type_name):
    from pympler import muppy
    import gc

    result = {}
    for obj in muppy.get_objects():
        for referent in muppy.get_referents(obj):
            if type(referent).__name__ == type_name:
                result.setdefault(referent, []).append(obj)
                if len(result) >= 100:
                    return result

    return result


from pprint import pprint
pprint(mem_obj_referrer("CurlWrapper"))

 

這段程式裡定義了一個 mem_obj_referrer() 函式,

給它一個型別的名稱,它就會從所有記憶體的物件中,

找出這物件參考到哪些東西 (referent),看看被參考到的是不是我們指定的型別。

這樣就能把參考到我們指定型別物件的「兇手」都找出來。

 

下面是我執行程式後的部分輸出結果,

可以發現 CurlWrapper 物件被 bound method CurlWrapper.pycurl_callback 參考到:

{<common.curlwrapper.CurlWrapper object at 0x7fd7866cb5d0>: [<bound method CurlWrapper.pycurl_callback of <common.curlwrapper.CurlWrapper object at 0x7fd7866cb5d0>>]}

 

這個 pycurl_callback() 是 CurlWrapper 物件的一個成員函式,

而 bound method 大概就像下面程式裡的 callback 變數,

它看似一個函式,但綁定了 curl_wrapper 這個物件:

curl_wrapper = CurlWrapper()
callback = curl_wrapper.pycurl_callback

 

單單這樣還不會造成循環參考… 真正的問題在於:

  • curl_wrapper 裡面有個 pycurl.Curl 物件
  • 我們將 callback 設給 pycurl.Curl 物件,作為回呼函式使用

 

以上導致 curl_wrapper 參考到 pycurl.Curl 物件,

而 pycurl.Curl 物件參考到 callback 這個 bound method,

連帶參考到它綁定的 curl_wrapper 物件,因而造成了循環參考。

 

知道原因的話,要解決就比較簡單了,

只要打斷至少其中一條鏈結就可以了,

像是在結束時,設定一個與 curl_wrapper 無關的函式給 pycurl.Curl 使用,

這樣 pycurl.Curl 就不再參考到 curl_wrapper 物件,

自然就可以被垃圾回收機制清掉了~

 

這個取得參考者的部分,我們用了 muppy.get_referents() ,

再去倒推參考到這個物件的人,感覺不太直覺。

原本有試過 gc.get_referrers() 函式,不過不知為何,

沒能列出參考到物件的人,但 muppy.get_referents() 倒推回去是可以的,

這也還是一個未解的謎囉~

 

修正程式後,看一下記憶體使用量的增長,

尾端看起來接近平順,應該已經沒有很明顯的記憶體洩露囉:

(本頁面已被瀏覽過 1,256 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料