一般來說在
Python 中,為了解決內(nèi)存泄漏問題,采用了對象引用計數(shù),并基于引用計數(shù)實現(xiàn)自動垃圾回收。
由于Python 有了自動垃圾回收功能,就造成了不少初學者誤認為自己從此過上了好日子,不必再受內(nèi)存泄漏的騷擾了。但如果仔細查看一下Python文檔對 __del__() 函數(shù)的描述,就知道這種好日子里也是有陰云的。下面摘抄一點文檔內(nèi)容如下:
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__() 函數(shù)的對象間的循環(huán)引用是導致內(nèi)存泄漏的主兇
。
另外需要說明:
對沒有 __del__() 函數(shù)的 Python 對象間的循環(huán)引用,是可以被自動垃圾回收掉的
。
如何知道一個對象是否內(nèi)存泄漏了呢?
方法一、當你認為一個對象應該被銷毀時(即引用計數(shù)為 0),可以通過 sys.getrefcount(obj) 來獲取對象的引用計數(shù),并根據(jù)返回值是否為 0 來判斷是否內(nèi)存泄漏。如果返回的引用計數(shù)不為 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()
在 test_gcleak() 中,設置垃圾回收器調(diào)試標志后,再用 collect() 進行垃圾回收,最后打印出該次垃圾回收發(fā)現(xiàn)的不可達的垃圾對象數(shù)和整個解釋器中的垃圾對象數(shù)。
gc.garbage 是一個 list 對象,列表項是垃圾收集器發(fā)現(xiàn)的不可達(即是垃圾對象)、但又不能釋放(即不能回收)的對象。文檔描述為:A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects).
通常,gc.garbage 中的對象是引用環(huán)中的對象。因為 Python 不知道按照什么樣的安全次序來調(diào)用環(huán)中對象的 __del__() 函數(shù),導致對象始終存活在 gc.garbage 中,造成內(nèi)存泄漏。如果知道一個安全的次序,那么就打破引用環(huán),再執(zhí)行 del gc.garbage[:] ,以清空垃圾對象列表。
上段代碼輸出為(#后字符串為筆者所加注釋):
#-----------------------------------------
begin leak test...
# 變量 _gcleak 的引用計數(shù)為 2.
_gcleak ref count0:2
# _gcleak 變?yōu)椴豢蛇_(unreachable)的非法變量.
_gcleak is invalid!
# 開始垃圾回收
begin collect...
# 本次垃圾回收發(fā)現(xiàn)的不可達的垃圾對象數(shù)為 0.
unreachable object num:0
# 整個解釋器中的垃圾對象數(shù)為 0.
garbage object num:0
#-----------------------------------------
由此可見 _gcleak 對象的引用計數(shù)是正確的,也沒有任何對象發(fā)生內(nèi)存泄漏。
如果不注釋掉 make_circle_ref() 中的 test_code_1 語句:
_gcleak._self = _gcleak
也就是讓 _gcleak 形成一個自己對自己的循環(huán)引用。再運行上述代碼,輸出結(jié)果就變成:
#-----------------------------------------
begin leak test...
_gcleak ref count0:3
_gcleak is invalid!
begin collect...
# 發(fā)現(xiàn)可以回收的垃圾對象: 地址為 012AA090,類型為 CGcLeak.
gc: uncollectable
gc: uncollectable
unreachable object num:2
#!! 不能回收的垃圾對象數(shù)為 1,導致內(nèi)存泄漏!
garbage object num:1
#-----------------------------------------
可見
{'_self': <__main__.CGcLeak object at 0x012AA090>, '_text': '##########'}
除了對自己的循環(huán)引用,多個對象間的循環(huán)引用也會導致內(nèi)存泄漏。簡單舉例如下:
#--------------- 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 ----------------
這次測試后輸出結(jié)果為:
#-----------------------------------------
begin leak test...
ref count0:a=3 b=3
_a is invalid!
_b is invalid!
begin collect...
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
unreachable object num:4
garbage object num:2
#-----------------------------------------
可見 _a,_b 對象都發(fā)生了內(nèi)存泄漏。因為二者是循環(huán)引用,垃圾回收器不知道該如何回收,也就是不知道該首先調(diào)用那個對象的 __del__() 函數(shù)。
采用以下任一方法,打破環(huán)狀引用,就可以避免內(nèi)存泄漏:
1.注釋掉 make_circle_ref() 中的 test_code_2 語句;
2.注釋掉 make_circle_ref() 中的 test_code_3 語句;
3.取消對 make_circle_ref() 中的 test_code_4 語句的注釋。
相應輸出結(jié)果變?yōu)椋?
#-----------------------------------------
begin leak test...
ref count0:a=2 b=3 # 注:此處輸出結(jié)果視情況變化.
_a is invalid!
_b is invalid!
begin collect...
unreachable object num:0
garbage object num:0
#-----------------------------------------
結(jié)論:Python 的 gc 有比較強的功能,比如設置 gc.set_debug(gc.DEBUG_LEAK) 就可以進行循環(huán)引用導致的內(nèi)存泄露的檢查。如果在開發(fā)時進行內(nèi)存泄露檢查;在發(fā)布時能夠確保不會內(nèi)存泄露,那么就可以延長 Python 的垃圾回收時間間隔、甚至主動關閉垃圾回收機制,從而提高運行效率。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

