比較 - JavaとPythonのガベージコレクションメソッドが異なるのはなぜですか?



java 機械学習 (6)

Pythonは、オブジェクトライフタイムを処理するために参照カウントメソッドを使用します。 したがって、それ以上使用しないオブジェクトはすぐに破棄されます。

ただし、Javaでは、GC(ガベージコレクター)は特定の時間に使用されなくなったオブジェクトを破棄します。

Javaがこの戦略を選択する理由と、この利点は何ですか?

これはPythonのアプローチよりも優れていますか?

https://ffff65535.com



JavaのトレースGCの大きな欠点の1つは、フルGCを実行するために、「世界を停止」し、比較的長時間アプリケーションをフリーズすることです。 ヒープが大きく、オブジェクトツリーが複雑な場合、数秒間フリーズします。 また、各フルGCはオブジェクトツリー全体を何度も訪問しますが、これはおそらく非常に非効率的です。 JavaがGCを行う方法のもう1つの欠点は、必要なヒープサイズをjvmに伝える必要があることです(デフォルトでは十分でない場合)。 JVMはその値からいくつかのしきい値を導き出し、ヒープ内のガベージスタックが多すぎる場合にGCプロセスをトリガーします。

これは、iOS(ObjectiveCに基づき、RCを使用)の滑らかさと比較して、実際には、最も高価な携帯電話でも、Android(Javaに基づく)のぎくしゃくした感じの主な原因であると思われます。

RCメモリ管理を有効にするjvmオプションが必要です。また、メモリがなくなったときに最後の手段としてのみGCを実行するようにします。


ダレン・トーマスが良い答えを与えます。 ただし、JavaアプローチとPythonアプローチの大きな違いの1つは、一般的なケース(循環参照なし)の参照カウントでは、オブジェクトが不確定な後日ではなくすぐにクリーンアップされることです。

たとえば、CPythonで次のようなずさんで移植性のないコードを書くことができます。

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

開いたファイルへの参照がなくなるとすぐにファイルがガベージコレクションされ、ファイル記述子が解放されるため、開いたファイルのファイル記述子はすぐにクリーンアップされます。 もちろん、Jython、IronPython、または場合によってはPyPyを実行する場合、ガベージコレクターは必ずしもずっと後まで実行されません。 おそらく、最初にファイル記述子が不足すると、プログラムがクラッシュします。

したがって、次のようなコードを記述する必要があります。

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

ただし、コードを少し短くすることがあるため、参照カウントに依存してリソースを常に解放したい場合があります。

最高のガベージコレクターは、最高のパフォーマンスを備えたガベージコレクターであると思います。現在、Javaスタイルの世代別ガベージコレクターは、個別のスレッドで実行でき、これらのクレイジーな最適化などをすべて備えています。コードはごくわずかであり、理想的には存在しないものである必要があります。


十分なメモリがある場合、ガベージコレクションは参照カウントよりも高速(時間効率が良い)です。 たとえば、コピーgcは「ライブ」オブジェクトをトラバースして新しいスペースにコピーし、メモリ領域全体をマークすることにより、すべての「デッド」オブジェクトをワンステップで回収できます。 十分なメモリがある場合 、これは非常に効率的です。 世代別コレクションは、「ほとんどのオブジェクトは若くして死ぬ」という知識を使用しています。 多くの場合、コピーする必要があるのは数パーセントのオブジェクトのみです。

[これは、gcがmalloc / freeよりも高速になる理由でもあります]

参照カウントは、ガベージコレクションよりもはるかにスペース効率が高くなります。これは、到達不能になった瞬間にメモリを再利用するためです。 これは、ファイナライザをオブジェクトにアタッチする場合に便利です(たとえば、Fileオブジェクトが到達不能になったらファイルを閉じます)。 参照カウントシステムは、空きメモリが数パーセントしかない場合でも機能します。 ただし、各ポインターの割り当てでカウンターをインクリメントおよびデクリメントしなければならない管理コストには多くの時間がかかり、サイクルを回収するには何らかの種類のガベージコレクションが依然として必要です。

したがって、トレードオフは明らかです。メモリに制約のある環境で作業する必要がある場合、または正確なファイナライザーが必要な場合は、参照カウントを使用します。 十分なメモリがあり、速度が必要な場合は、ガベージコレクションを使用します。


参照カウントを使用することには欠点があります。 最も言及されているものの1つは循環参照です。AがBを参照し、BがCを参照し、CがCを参照すると仮定します。従来の参照カウント。 CPython(参照カウントはpython自体の一部ではなく、そのC実装の一部)は、定期的に実行される個別のガベージコレクションルーチンで循環参照をキャッチします...

別の欠点:参照カウントは実行を遅くする可能性があります。 オブジェクトが参照および参照解除されるたびに、インタープリター/ VMは、カウントが0になったかどうかを確認する必要があります(そして、必要に応じて割り当てを解除します)。 ガベージコレクションはこれを行う必要はありません。

また、ガベージコレクションは別のスレッドで実行できます(ただし、少し注意が必要です)。 大量のRAMを搭載したマシンや、メモリの使用速度が遅いプロセスでは、GCをまったく実行したくない場合があります。 参照カウントは、パフォーマンスの点で少し欠点になります...


実際には、参照カウントとSun JVMで使用される戦略は、すべて異なる種類のガベージコレクションアルゴリズムです。

死んだオブジェクトを追跡するには、トレースと参照カウントの2つのアプローチがあります。 トレースでは、GCは「ルート」から始まります-スタック参照など、すべての到達可能な(ライブ)オブジェクトをトレースします。 到達できないものはすべて死んでいると見なされます。 参照が変更されるたびに参照をカウントする際、関連するオブジェクトのカウントが更新されます。 参照カウントがゼロに設定されたオブジェクトはすべてデッドと見なされます。

基本的にすべてのGC実装にはトレードオフがありますが、トレースは通常、高スループット(高速)操作には適していますが、休止時間が長くなります(UIまたはプログラムがフリーズする可能性のある大きなギャップ)。 参照カウントは小さなチャンクで動作できますが、全体的に遅くなります。 これは、フリーズが少ないことを意味しますが、全体的にパフォーマンスが低下します。

さらに、参照カウントGCには、参照カウントだけでキャッチされないサイクル内のオブジェクトをクリーンアップするためのサイクル検出器が必要です。 Perl 5のGC実装にはサイクル検出器がなく、周期的なメモリリークが発生する可能性がありました。

また、両方の長所(短い休止時間、高いスループット)を得るための研究も行われています: http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf : http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf





garbage-collection