<轉>Python的內存泄漏及gc模塊的使用分析

通常來講在 Python 中,爲了解決內存泄漏問題,採用了對象引用計數,並基於引用計數實現自動垃圾回收。
因爲Python 有了自動垃圾回收功能,就形成了很多初學者誤認爲本身今後過上了好日子,沒必要再受內存泄漏的騷擾了。但若是仔細查看一下Python文檔對 __del__() 函數的描述,就知道這種好日子裏也是有陰雲的。下面摘抄一點文檔內容以下:安全

Some common situations that may prevent the reference count of an object from going to zero include: circular references between objects (e.g., a doubly-linked list or a tree data structure with parent and child pointers); a reference to the object on the stack frame of a function that caught an exception (the traceback stored in sys.exc_traceback keeps the stack frame alive); or a reference to the object on the stack frame that raised an unhandled exception in interactive mode (the traceback stored in sys.last_traceback keeps the stack frame alive).函數

 

可見,有 __del__() 函數的對象間的循環引用是致使內存泄漏的主兇。
另外須要說明:對沒有 __del__() 函數的 Python 對象間的循環引用,是能夠被自動垃圾回收掉的。測試

如何知道一個對象是否內存泄漏了呢?spa

方法1、當你認爲一個對象應該被銷燬時(即引用計數爲 0),能夠經過 sys.getrefcount(obj) 來獲取對象的引用計數,並根據返回值是否爲 0 來判斷是否內存泄漏。若是返回的引用計數不爲 0,說明在此刻對象 obj 是不能被垃圾回收器回收掉的。debug

方法2、也能夠經過 Python 擴展模塊 gc 來查看不能回收的對象的詳細信息。調試


首先,來看一段正常的測試代碼:code

#--------------- code begin --------------
# -*- coding: utf-8 -*-
import gc
import sys

class CGcLeak(object):
  def __init__(self):
    self._text = '#'*10

  def __del__(self):
    pass

def make_circle_ref():
  _gcleak = CGcLeak()
#  _gcleak._self = _gcleak # test_code_1
  print '_gcleak ref count0:%d' % sys.getrefcount(_gcleak)
  del _gcleak
  try:
    print '_gcleak ref count1:%d' % sys.getrefcount(_gcleak)
  except UnboundLocalError:
    print '_gcleak is invalid!'

def test_gcleak():
  # Enable automatic garbage collection.
  gc.enable()
  # Set the garbage collection debugging flags.
  gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | /
    gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS)

  print 'begin leak test...'
  make_circle_ref()

  print 'begin collect...'
  _unreachable = gc.collect()
  print 'unreachable object num:%d' % _unreachable
  print 'garbage object num:%d' % len(gc.garbage)

if __name__ == '__main__':
  test_gcleak()

 

在 test_gcleak() 中,設置垃圾回收器調試標誌後,再用 collect() 進行垃圾回收,最後打印出該次垃圾回收發現的不可達的垃圾對象數和整個解釋器中的垃圾對象數。對象

gc.garbage 是一個 list 對象,列表項是垃圾收集器發現的不可達(便是垃圾對象)、但又不能釋放(即不能回收)的對象。文檔描述爲:A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects).
一般,gc.garbage 中的對象是引用環中的對象。由於 Python 不知道按照什麼樣的安全次序來調用環中對象的 __del__() 函數,致使對象始終存活在 gc.garbage 中,形成內存泄漏。若是知道一個安全的次序,那麼就打破引用環,再執行 del gc.garbage[:] ,以清空垃圾對象列表。blog

上段代碼輸出爲(#後字符串爲筆者所加註釋):內存

#-----------------------------------------
begin leak test...
# 變量 _gcleak 的引用計數爲 2.
_gcleak ref count0:2
# _gcleak 變爲不可達(unreachable)的非法變量.
_gcleak is invalid!
# 開始垃圾回收
begin collect...
# 本次垃圾回收發現的不可達的垃圾對象數爲 0.
unreachable object num:0
# 整個解釋器中的垃圾對象數爲 0.
garbage object num:0
#-----------------------------------------

 

因而可知 _gcleak 對象的引用計數是正確的,也沒有任何對象發生內存泄漏。

若是不註釋掉 make_circle_ref() 中的 test_code_1 語句:

_gcleak._self = _gcleak

 

也就是讓 _gcleak 造成一個本身對本身的循環引用。再運行上述代碼,輸出結果就變成:

#-----------------------------------------
begin leak test...
_gcleak ref count0:3
_gcleak is invalid!
begin collect...
# 發現能夠回收的垃圾對象: 地址爲 012AA090,類型爲 CGcLeak.
gc: uncollectable <CGcLeak 012AA090>
gc: uncollectable <dict 012AC1E0>
unreachable object num:2
#!! 不能回收的垃圾對象數爲 1,致使內存泄漏!
garbage object num:1
#-----------------------------------------

 

可見 <CGcLeak 012AA090> 對象發生了內存泄漏!!而多出的 dict 垃圾就是泄漏的 _gcleak 對象的字典,打印出字典信息爲:

{'_self': <__main__.CGcLeak object at 0x012AA090>, '_text': '##########'}

除了對本身的循環引用,多個對象間的循環引用也會致使內存泄漏。簡單舉例以下:

#--------------- code begin --------------

class CGcLeakA(object):
  def __init__(self):
    self._text = '#'*10

  def __del__(self):
    pass

class CGcLeakB(object):
  def __init__(self):
    self._text = '*'*10

  def __del__(self):
    pass

def make_circle_ref():
  _a = CGcLeakA()
  _b = CGcLeakB()
  _a._b = _b # test_code_2
  _b._a = _a # test_code_3
  print 'ref count0:a=%d b=%d' % /
    (sys.getrefcount(_a), sys.getrefcount(_b))
#  _b._a = None  # test_code_4
  del _a
  del _b
  try:
    print 'ref count1:a=%d' % sys.getrefcount(_a)
  except UnboundLocalError:
    print '_a is invalid!'
  try:
    print 'ref count2:b=%d' % sys.getrefcount(_b)
  except UnboundLocalError:
    print '_b is invalid!'

#--------------- code end ----------------

 

此次測試後輸出結果爲:

#-----------------------------------------
begin leak test...
ref count0:a=3 b=3
_a is invalid!
_b is invalid!
begin collect...
gc: uncollectable <CGcLeakA 012AA110>
gc: uncollectable <CGcLeakB 012AA0B0>
gc: uncollectable <dict 012AC1E0>
gc: uncollectable <dict 012AC0C0>
unreachable object num:4
garbage object num:2
#-----------------------------------------

 

可見 _a,_b 對象都發生了內存泄漏。由於兩者是循環引用,垃圾回收器不知道該如何回收,也就是不知道該首先調用那個對象的 __del__() 函數。

採用如下任一方法,打破環狀引用,就能夠避免內存泄漏:

1.註釋掉 make_circle_ref() 中的 test_code_2 語句;
2.註釋掉 make_circle_ref() 中的 test_code_3 語句;
3.取消對 make_circle_ref() 中的 test_code_4 語句的註釋。

相應輸出結果變爲:

#-----------------------------------------
begin leak test...
ref count0:a=2 b=3 # 注:此處輸出結果視狀況變化.
_a is invalid!
_b is invalid!
begin collect...
unreachable object num:0
garbage object num:0
#-----------------------------------------

 

結論:Python 的 gc 有比較強的功能,好比設置 gc.set_debug(gc.DEBUG_LEAK) 就能夠進行循環引用致使的內存泄露的檢查。若是在開發時進行內存泄露檢查;在發佈時可以確保不會內存泄露,那麼就能夠延長 Python 的垃圾回收時間間隔、甚至主動關閉垃圾回收機制,從而提升運行效率。

相關文章
相關標籤/搜索