Java と Python のガベージ コレクション方法が異なるのはなぜですか?

StackOverflow https://stackoverflow.com/questions/21934

  •  09-06-2019
  •  | 
  •  

質問

Python は参照カウント方式を使用してオブジェクトの有効期間を処理します。したがって、用途がなくなったオブジェクトはすぐに破棄されます。

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

なぜ Java はこの戦略を選択するのでしょうか?また、これによる利点は何ですか?

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

役に立ちましたか?

解決

参照カウントの使用には欠点があります。最も言及されているものの 1 つは循環参照です。A が B を参照し、B が C を参照し、C が B を参照するとします。A が B への参照を削除した場合、B と C の両方の参照カウントは 1 のままであり、従来の参照カウントでは削除されません。CPython (参照カウントは Python 自体の一部ではなく、その C 実装の一部です) は、定期的に実行される別のガベージ コレクション ルーチンで循環参照を捕捉します。

もう一つの欠点:参照カウントにより実行が遅くなる可能性があります。オブジェクトが参照および逆参照されるたびに、インタプリタ/VM はカウントが 0 になったかどうかを確認する必要があります (カウントが 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

ダレン・トーマスは良い答えをくれます。ただし、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 スタイルの世代別ガベージ コレクターのようです。コードの記述方法との違いはごくわずかであり、存在しないことが理想的です。

記事だと思います」Java の理論と実践:ガベージ コレクションの簡単な歴史IBM の「」は、あなたが抱いている疑問のいくつかを説明するのに役立つはずです。

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

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

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

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

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

これが、iOS (ObjectiveC ベース、RC 使用) の滑らかさと比較して、最も高価な携帯電話であっても Android (Java ベース) のぎくしゃく感の主な原因であると推測します。

RC メモリ管理を有効にするための jvm オプションを見てみたいと思っています。メモリがもうなくなった場合の最後の手段として GC を実行するだけにしておいてもよいでしょう。

最新の Sun Java VM には、実際に調整できる複数の GC アルゴリズムがあります。Java VM 仕様では、VM ごとに異なる (複数の) GC アルゴリズムを使用できるように、実際の G​​C 動作の指定を意図的に省略しました。

たとえば、デフォルトの Sun Java VM GC 動作の「stop-the-world」アプローチを嫌う人のために、次のような VM があります。 IBMのWebSphere Real Time これにより、リアルタイム アプリケーションを Java 上で実行できるようになります。

Java VM 仕様は公開されているため、CPython の GC アルゴリズムを使用する Java VM を実装することを (理論的には) 妨げるものはありません。

参照カウントは、マルチスレッド環境で効率的に行うのが特に困難です。ハードウェア支援トランザクションや同様の (現時点では) 通常ではないアトミック命令を使用せずに、どのようにしてそれをやり始めることができるのかわかりません。

参照カウントは簡単に実装できます。JVM は競合する実装に多額の資金をつぎ込んでいるので、非常に困難な問題に対して非常に優れたソリューションを実装していることは驚くべきことではありません。ただし、JVM でお気に入りの言語をターゲットにすることがますます簡単になってきています。

終盤になりましたが、Python での RC の重要な根拠の 1 つはその単純さだと思います。これを参照してください アレックス・マルテッリからのメール, 、 例えば。

(Google キャッシュの外のリンク、Python リストの 2005 年 10 月 13 日の電子メールの日付が見つかりませんでした)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top