導航

          <2009年11月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          留言簿(4)

          隨筆分類

          隨筆檔案

          閱讀排行榜

          評論排行榜

          常用鏈接

          統計

          最新評論

          Python 循環引用導致內存泄漏的內存管理TEST ref from:http://meizhini.javaeye.com/blog/249716

            在 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 對象間的循環引用,是可以被自動垃圾回收掉的。


          有 __del__() 函數的對象間的循環引用是導致內存泄漏的主兇。特別說明:對沒有 __del__() 函數的 Python 對象間的循環引用,是可以被自動垃圾回收掉的。所以,沒什么事,千萬不要輕易啟用__del__操作。
          如何知道一個對象是否內存泄漏了呢?
              方法一、當你認為一個對象應該被銷毀時(即引用計數為 0),可以通過 sys.getrefcount(obj) 來獲取對象的引用計數,并根據返回值是否為 0 來判斷是否內存泄漏。如果返回的引用計數不為 0,說明在此刻對象 obj 是不能被垃圾回收器回收掉的。
              方法二、也可以通過 Python 擴展模塊 gc 來查看不能回收的對象的詳細信息。


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

          #--------------- 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()

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

              在 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[:] ,以清空垃圾對象列表。

              上段代碼輸出為(#后字符串為筆者所加注釋):
          #-----------------------------------------
          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 的垃圾回收時間間隔、甚至主動關閉垃圾回收機制,從而提高運行效率。

          posted on 2009-11-30 10:47 九寶 閱讀(1330) 評論(0)  編輯  收藏 所屬分類: Python

          主站蜘蛛池模板: 林西县| 东源县| 高安市| 德阳市| 文成县| 陆川县| 疏勒县| 广平县| 营口市| 美姑县| 三都| 旬阳县| 克东县| 布拖县| 旺苍县| 弥勒县| 新龙县| 通城县| 将乐县| 海盐县| 怀仁县| 竹北市| 若尔盖县| 遂昌县| 左权县| 桂东县| 呼伦贝尔市| 乐昌市| 新闻| 浠水县| 麻阳| 庆安县| 桂平市| 崇文区| 都昌县| 汕头市| 永州市| 达孜县| 丁青县| 冕宁县| 广汉市|