ホームページ > バックエンド開発 > Python チュートリアル > Python のメモリ リークの分析と gc モジュールの使用法

Python のメモリ リークの分析と gc モジュールの使用法

WBOY
リリース: 2016-06-16 08:43:13
オリジナル
1247 人が閲覧しました

一般的に、 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 オブジェクトにメモリ リークがあります。 !余分な 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 オブジェクトの両方でメモリ リークが発生していることがわかります。この 2 つは循環参照であるため、ガベージ コレクターはそれらをリサイクルする方法がわかりません。つまり、どのオブジェクトの __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 のガベージ コレクション間隔を延長したり、ガベージ コレクション メカニズムを積極的にオフにしたりすることができるため、操作効率が向上します。
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート