Pythonは、参照カウントメソッドを使用してオブジェクトの有効期間を処理します。したがって、使用されなくなったオブジェクトはすぐに破棄されます。
ただし、Javaでは、GC(ガベージコレクター)は、特定の時間に使用されなくなったオブジェクトを破棄します。
Javaがこの戦略を選択する理由と、これによるメリットは何ですか?
これはPythonのアプローチよりも優れていますか?
Pythonは、参照カウントメソッドを使用してオブジェクトの有効期間を処理します。したがって、使用されなくなったオブジェクトはすぐに破棄されます。
ただし、Javaでは、GC(ガベージコレクター)は、特定の時間に使用されなくなったオブジェクトを破棄します。
Javaがこの戦略を選択する理由と、これによるメリットは何ですか?
これはPythonのアプローチよりも優れていますか?
参照カウントの使用には欠点があります。最も言及されているのは循環参照です: A が B を参照し、B が C を参照し、C が B を参照するとします。従来の参照カウントを使用します。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
Darren Thomas が良い答えを出しています。ただし、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 のトレース GC の大きな欠点の 1 つは、完全な GC を実行するために「世界が停止」し、アプリケーションが比較的長時間フリーズすることがあるということです。ヒープが大きく、オブジェクト ツリーが複雑な場合、数秒間フリーズします。また、各フル GC はオブジェクト ツリー全体を何度も訪問しますが、これはおそらく非常に非効率的です。Java が GC を行う方法のもう 1 つの欠点は、必要なヒープ サイズを jvm に伝える必要があることです (デフォルトが十分でない場合)。JVM は、その値からいくつかのしきい値を導出します。このしきい値は、ヒープに大量のガベージが積み重なっている場合に GC プロセスをトリガーします。
iOS (ObjectiveC ベースで RC を使用) の滑らかさと比較して、最も高価な携帯電話でも Android (Java ベース) のぎくしゃくした感じの主な原因はこれだと思います。
RC メモリ管理を有効にする jvm オプションと、残りのメモリがなくなったときの最後の手段として GC のみを実行することを期待しています。
ガベージ コレクションは、十分なメモリがあれば、参照カウントよりも高速です (時間効率が高くなります)。たとえば、コピー gc は「生きている」オブジェクトをトラバースしてそれらを新しいスペースにコピーし、メモリ領域全体をマークすることですべての「死んだ」オブジェクトを 1 ステップで回収できます。十分なメモリがある場合、これは非常に効率的です。世代別コレクションは、「ほとんどのオブジェクトは若くして死ぬ」という知識を使用します。多くの場合、オブジェクトの数パーセントのみをコピーする必要があります。
[これは、gc が malloc/free よりも高速である理由でもあります]
参照カウントは、到達不能になった瞬間にメモリを再利用するため、ガベージ コレクションよりもはるかにスペース効率が高くなります。これは、ファイナライザーをオブジェクトにアタッチしたい場合に便利です (たとえば、File オブジェクトに到達できなくなったときにファイルを閉じる場合など)。参照カウント システムは、空きメモリが数パーセントしかない場合でも機能します。しかし、ポインターの割り当てごとにカウンターをインクリメントおよびデクリメントしなければならないという管理コストには多くの時間がかかり、サイクルを再利用するには何らかの種類のガベージ コレクションが必要です。
したがって、トレードオフは明らかです。メモリに制約のある環境で作業する必要がある場合、または正確なファイナライザーが必要な場合は、参照カウントを使用してください。十分なメモリがあり、速度が必要な場合は、ガベージ コレクションを使用してください。
最新のSunJavaVMには、実際には微調整できる複数のGCアルゴリズムがあります。Java VM仕様では、実際のGC動作の指定を意図的に省略して、VMごとに異なる(および複数の)GCアルゴリズムを許可しています。
たとえば、デフォルトのSun Java VM GC動作の「ストップ・ザ・ワールド」アプローチを嫌うすべての人のために、リアルタイムアプリケーションをJavaで実行できるようにするIBMのWebSphereRealTimeなどのVMがあります。
Java VM仕様は公開されているため、(理論的には)CPythonのGCアルゴリズムを使用するJavaVMの実装を妨げるものは何もありません。
参照カウントは、マルチスレッド環境で効率的に行うのが特に困難です。ハードウェア支援のトランザクションや同様の (現在のところ) 異常なアトミック命令を使用せずに、どのようにそれを開始するかはわかりません。
参照カウントは簡単に実装できます。JVM は競合する実装に多額の資金を投入してきたため、非常に困難な問題に対して非常に優れたソリューションを実装していることは驚くべきことではありません。ただし、JVM でお気に入りの言語をターゲットにすることがますます簡単になってきています。