序文
Pythonでは、すべてがオブジェクトであり、すべての変数割り当てはオブジェクト参照メカニズムに従います。プログラムの実行中は、操作中に生成された一時変数を格納するためのメモリ内のスペースを開く必要があり、計算が完了した後、結果が永続メモリに出力されます。データの量が多すぎる場合、メモリ空間管理が不十分であると、一般にバーストメモリと呼ばれるOOM(メモリ不足)が発生しやすく、プログラムがオペレーティングシステムによって中止される場合があります。サーバーの場合、メモリ管理がより重要です。そうでない場合、メモリリークが発生しやすくなります。ここでのリークは、メモリに情報セキュリティの問題があり、悪意のあるプログラムによって使用されていることを意味しませんが、プログラム自体は適切に設計されていません。プログラムが、使用されなくなったメモリを解放できないようにします。-メモリリークは、メモリが物理的に消えたことを意味するのではなく、コードがメモリの特定のセクションを割り当てた後、設計エラーのためにメモリのこのセクションの制御を失い、メモリの浪費を招くことを意味します。つまり、このメモリはgcの制御外です
Pythonのすべてがオブジェクトであるため、表示されるすべての変数は基本的にオブジェクトへのポインタです。オブジェクトが呼び出されなくなったとき、つまり、オブジェクトの参照カウント(ポインター番号)が0のときは、オブジェクトに到達できず、当然ガベージになり、リサイクルする必要があることを意味します。それを指す変数がないことは簡単に理解できます。
インポートOS インポートpsutil #の番組プログラムのPythonの現在のメモリサイズ DEF show_memory_info(ヒント): PID = os.getpid() P = psutil.Process(PID) 情報 = p.memory_full_info() メモリ = info.uss / 1024./ 1024 印刷({}メモリ使用:{} MB .format(ヒント、メモリ))
関数func()が呼び出されていることがわかりますリストaが作成された後、メモリ使用量は433 MBまで急速に増加します:関数呼び出しが終了すると、メモリは通常に戻ります。これは、関数内で宣言されたリストaがローカル変数であるためです。関数が戻った後、ローカル変数の参照はキャンセルされます。このとき、リストaによって参照されるオブジェクトの参照番号は0であり、Pythonはガベージコレクションを実行するため、以前に使用されていた大量のメモリが元に戻りました。
DEF FUNC(): show_memory_info( 初期 ) グローバルAは A = [I 用 I における 範囲(10000000 )] show_memory_info(後に作成) FUNC() show_memory_info( 完成 ) ##########输出### ####### 初期メモリを使用:48.88671875 MB 使用し作成されたメモリの後: 433.94921875 MB 完成したメモリを使用: 433.94921875メガバイトを
新しいコードでは、グローバルaはaをグローバル変数として宣言することを意味します。次に、関数が戻った後でも、リスト参照が存在するため、オブジェクトはガベージコレクションされず、多くのメモリを消費します。同様に、生成されたリストを返し、それをメインプログラムで受け取った場合、参照はまだ存在し、ガベージコレクションはトリガーされず、多くのメモリがまだ占有されています。
デフFUNC(): show_memory_info(初期) = [I 用 I で 狂わせる(10000000 )] show_memory_info(作成した後に) 返し 、A = FUNC() show_memory_infoを(完成品)##########输出# ######### 初期メモリを使用:47.96484375 MB 使用し作成されたメモリの後: 434.515625 MB 完成したメモリを使用: 434.515625メガバイトを
変数が参照されている回数を確認するにはどうすればよいですか?sys.getrefcountを使用して
インポートSYS A = [] #回の参考文献は、GETREFCOUNTからから1 プリント(sys.getrefcount(A)) DEF FUNC(A): #4の参考文献は、Pythonの関数呼び出しスタック、関数パラメータ、およびGETREFCOUNT 印刷(sys.getrefcount(A)) FUNC(A) #GETREFCOUNTから2つの参照から1、存在しない関数funcを呼び出して 印刷を(sys.getrefcount(A)) ######## ##出力########## 2 4 2
関数呼び出しが含まれる場合は、2つの1が追加されます。関数スタック2.関数呼び出し
ここから、PythonはCのようにメモリを解放する必要がないことがわかりますが、Pythonは手動でメモリを解放する方法も提供しますgc.collect()
インポートGC show_memory_info(初期) [I = 用 I における範囲(10000000 )] show_memory_info(後に作成された) デルA gc.collect() show_memory_info(仕上がり) プリント() ##########输出########## 初期メモリ使用:48.1015625 MB 作成したメモリを使用した後: 434.3828125 MBの 仕上げメモリ使用: 48.33203125 MB -------------------- -------------------------------------------------- ----- NameErrorTraceback(最新の呼び出しの最後) で 11 12 show_memory_info(finish) ---> 13 print (a) NameError:名前aが定義されていません
今のところ、Pythonのガベージコレクションのメカニズムは非常に単純なようです。オブジェクト参照の数が0である限り、それはgcによってトリガーされる必要があります。0の参照番号は、gcをトリガーするのに十分かつ必要な条件ですか?
2つのオブジェクトが相互に参照していて、他のオブジェクトから参照されなくなった場合、それらはガベージコレクションされますか?
def func(): show_memory_info(initial) a = [i for i in range(10000000 )] b = [i for i in range(10000000 )] show_memory_info(after a、b created) a.append(b) b.append () FUNC() show_memory_info(完成) ##########输出########## 47.984375:使用される初期メモリMB :使用、B作成メモリ後 822.73828125 MB 使用完成メモリ: 821.73046875 MB
結果から、それらが回復されていないことは明らかですが、手続きの観点から、この関数が終了すると、ローカル変数としてのaおよびbはプログラムの意味で存在しなくなります。ただし、相互参照のため、参照番号はゼロではありません。現時点でそれを回避する方法
-
1.コードを論理的に修正して、このような循環参照を回避する
-
2.手作業によるリサイクル
import gc def func(): show_memory_info(initial) a = [i for i in range(10000000 )] b = [i for i in range(10000000 )] show_memory_info(after a、b created) a.append(b) b .append() FUNC() gc.collect() show_memory_info(完成品) ##########输出########## 49.51171875:使用する初期メモリMB 、Bを作成した後メモリ使用済み: 824.1328125 MB 使用済み メモリ: 49.98046875 MB
循環参照の場合、Pythonには自動ガベージコレクションアルゴリズム1.マークスイープアルゴリズム2.世代別コレクションがあります。
マークをクリアする手順は、次のようにまとめられます。1. GCはすべての「アクティブオブジェクト」をマークします2.「非アクティブオブジェクト」とマークされていないオブジェクトをリサイクルしますPythonはどのようにして非アクティブオブジェクトを判断するのですか?グラフ理論を使用して到達不能の概念を理解する。有向グラフの場合、ノードからトラバースを開始し、通過するすべてのノードにマークを付けると、トラバースが終了した後、マークされていないすべてのノードを到達不能ノードと呼びます。もちろん、これらのノードの存在は意味がありませんが、当然、それらをリサイクルする必要があります。ただし、グラフ全体を毎回走査することは、Pythonのパフォーマンスの大きな浪費です。したがって、Pythonのガベージコレクションの実装では、マークスイープは二重にリンクされたリストを使用してデータ構造を維持し、コンテナータイプのオブジェクトのみを考慮します(コンテナータイプのオブジェクト、リスト、dict、タプル、インスタンスのみが循環参照を持つことができます) 。
この図では、小さな黒い円はグローバル変数と見なされます。つまり、ルートオブジェクトと見なされます。小さな黒い円から、オブジェクト1に直接到達でき、次にマークされ、オブジェクト2、3に間接的に到達してマークされます。4 5で到達できない場合、1、2、3はアクティブなオブジェクト、4と5は非アクティブなオブジェクトであり、GCによってリサイクルされます。
世代別リサイクルとは、時間に対するスペースの操作方法です。Pythonは、オブジェクトの生存時間に応じてメモリを異なるセットに分割します。各セットは世代と呼ばれます。Pythonはメモリを3つの「世代」に分割します。これは若い世代です。 (第0世代)、中間世代(第1世代)、および古い世代(第2世代)、これらは3つのリンクリストに対応し、ガベージコレクションの頻度はオブジェクトの生存時間の増加に伴って減少します。新しく作成されたオブジェクトは若い世代に割り当てられます。若い世代のリンクリストの総数が上限に達すると(新しいオブジェクトからガベージコレクターで削除されたオブジェクトを差し引いた値が対応するしきい値に達すると)、Pythonガベージコレクションメカニズムがトリガーされます。リサイクルされたオブジェクトはリサイクルされ、リサイクルされなかったオブジェクトは中年に移動されます。古い時代のオブジェクトは、最も長く存続するオブジェクトであり、システムのライフサイクル全体で存続します。 。同時に、世代別リサイクルはマーク除去技術に基づいています。実際、世代別リサイクルは、新しく生まれたオブジェクトはガベージコレクションされる可能性が高く、寿命が長いオブジェクトは存続する可能性が高いという考えに基づいています。したがって、このアプローチにより、多くの計算を節約でき、Pythonのパフォーマンスが向上します。したがって、今の質問では、参照カウントはgcをトリガーするための十分な必須条件ではなく、循環参照もトリガーされます。
objgraphを使用してプログラムをデバッグできます。公式ドキュメントは注意深く読まれていないため、参照用にここにドキュメントを置くことしかできません〜2つの関数が非常に便利です1. show_refs()2. show_backrefs()
元のテキスト:https : //developer.51cto.com/art/201912/607082.htm
この記事のアドレス:https : //www.linuxprobe.com/python-garbage-collection.html