一般的に、 Python では、メモリ リークの問題を解決するために、オブジェクトの参照カウントが使用され、参照カウントに基づいて自動ガベージ コレクションが実装されます。
Python には自動ガベージ コレクションがあるため、多くの初心者は、それ以来良い生活を送っており、メモリ リークに悩まされる必要がなくなったと誤解しています。しかし、Python ドキュメントの __del__() 関数の説明を注意深く見ると、このような好景気にも雲があることがわかります。以下はドキュメントからの抜粋です:
オブジェクトの参照カウントがゼロになるのを妨げる一般的な状況としては、次のようなものがあります。オブジェクト間の循環参照 (例: オブジェクトへの参照が二重にリンクされたリストや、親ポインターと子ポインターを持つツリー データ構造)。例外をキャッチした関数のスタック フレーム上 (sys.exc_traceback に保存されたトレースバックにより、スタック フレームが維持されます)、または対話モードで未処理の例外を発生させたスタック フレーム上のオブジェクトへの参照 (sys.exc_traceback に保存されたトレースバック)。 .last_traceback はスタック フレームを維持します)。
__del__() 関数によるオブジェクト間の循環参照がメモリ リークの主な原因であることがわかります。
さらに、__del__() 関数を使用しない Python オブジェクト間の循環参照は、自動的にガベージ コレクションされる可能性があります 。
オブジェクトにメモリ リークがあるかどうかを確認するにはどうすればよいですか?
方法 1. オブジェクトを破棄する必要があると思われる場合 (つまり、参照カウントが 0 である場合)、sys.getrefcount(obj) を通じてオブジェクトの参照カウントを取得し、メモリ リークがあるかどうかを判断できます。戻り値が 0 かどうかに基づきます。返された参照カウントが 0 でない場合は、現時点ではオブジェクト obj をガベージ コレクターでリサイクルできないことを意味します。
方法 2: 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() では、ガベージ コレクターのデバッグ フラグを設定した後、collect() を使用してガベージ コレクションを実行し、最後に、このガベージ コレクションで見つかった到達不能なガベージ オブジェクトの数とインタープリター全体のガベージ オブジェクトの数を出力します。 。
gc.garbage はリスト オブジェクトです。リスト項目は、ガベージ コレクターによって到達不能であることが検出されたものの、解放できない (つまり、リサイクルできない) オブジェクトです。ドキュメントの説明は次のとおりです: コレクターが到達不能であることが判明したが解放できなかったオブジェクト (収集不可能なオブジェクト) のリスト。
通常、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 #-----------------------------------------
Visible
{'_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 #-----------------------------------------
メモリ リークを避けるために、次のいずれかの方法を使用して循環参照を解除します。
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 #-----------------------------------------