web-dev-qa-db-ja.com

Javaオブジェクトが参照されなくなった直後に削除されないのはなぜですか?

Javaでは、オブジェクトへの参照がなくなるとすぐに削除の対象になりますが、JVMはオブジェクトが実際に削除されるタイミングを決定します。 Objective-Cの用語を使用する場合、すべてのJava参照は本質的に「強力」です。ただし、Objective-Cでは、オブジェクトに強い参照がなくなった場合、オブジェクトはすぐに削除されます。なぜですか。これはJavaの場合ではありませんか?

79
moonman239

まず、Javaには弱い参照と、ソフト参照と呼ばれる別のベストエフォートカテゴリがあります。弱い参照と強い参照は、参照カウントとガベージコレクションとはまったく別の問題です。

第2に、スペースを犠牲にすることにより、ガベージコレクションを時間的に効率化できるメモリ使用量のパターンがあります。たとえば、新しいオブジェクトは古いオブジェクトよりもはるかに削除される可能性が高くなります。したがって、スイープの間に少し待つと、少数のサバイバーを長期間のストレージに移動しながら、新世代のメモリのほとんどを削除できます。その長期保存は、それほど頻繁にスキャンできません。手動のメモリ管理または参照カウントによる即時削除は、断片化を起こしやすくなります。

それは、給料で1回食料品の買い物をすることと、1日に十分な食料を得るために毎日行くことの違いのようなものです。 1回の大きな旅行は、個々の小さな旅行よりもはるかに長くかかりますが、全体的には時間とおそらくお金を節約することになります。

79
Karl Bielefeldt

何かが参照されなくなったことを正しく知ることは容易ではないからです。簡単にはほど遠い。

2つのオブジェクトが相互に参照している場合はどうなりますか?彼らは永遠に滞在しますか?その考え方を任意のデータ構造の解決に拡張すると、JVMまたは他のガベージコレクターが、何がまだ必要で何が可能かを判断するためのはるかに洗練された方法を採用せざるを得ない理由がすぐにわかります。

86
whatsisname

Objective-Cの用語を使用する場合、すべてのJava参照は本質的に「強力」です。

不正解です-Javaには弱参照とソフト参照の両方がありますが、これらは言語キーワードとしてではなくオブジェクトレベルで実装されています。

Objective-Cでは、オブジェクトに強い参照がなくなった場合、オブジェクトはすぐに削除されます。

これも必ずしも正しいとは限りません。ObjectiveCの一部のバージョンでは、実際に世代別ガベージコレクターが使用されていました。他のバージョンでは、ガベージコレクションはまったくありませんでした。

Objective Cの新しいバージョンでは、トレースベースのGCではなく自動参照カウント(ARC)を使用しているため、これは(多くの場合)参照カウントがゼロになるとオブジェクトが「削除」されることになります。ただし、JVM実装も準拠している可能性があり、正確にこのように機能することに注意してください(つまり、準拠していて、GCがまったくない可能性があります)。

では、なぜほとんどのJVM実装がこれを行わず、代わりにトレースベースのGCアルゴリズムを使用するのでしょうか。

簡単に言えば、ARCは最初に思われるほどユートピア的ではありません。

  • 参照がコピー、変更、または範囲外になるたびにカウンターをインクリメントまたはデクリメントする必要があるため、明らかなパフォーマンスオーバーヘッドが発生します。
  • ARCは、循環参照がすべて相互に参照しているため、参照カウントがゼロになることはないため、循環参照を簡単にクリアできません。

もちろん、ARCには利点があります。実装が簡単で、収集は確定的です。しかし、とりわけ上記の欠点は、JVM実装の大部分が世代別のトレースベースのGCを使用する理由です。

23
berry120

Javaはオブジェクトが収集されるタイミングを正確に指定しません。これにより、実装ではガベージコレクションの処理方法を自由に選択できるようになります。

多くの異なるガベージコレクションメカニズムがありますが、オブジェクトをすぐに収集できることを保証するメカニズムは、ほぼ完全に参照カウントに基づいています(この傾向を打破するアルゴリズムは知りません)。参照カウントは強力なツールですが、参照カウントを維持するという犠牲が伴います。シングルスレッドコードでは、それはインクリメントとデクリメントに過ぎないため、ポインターを割り当てると、参照カウントコードでは、非参照カウントコードの場合の3倍のコストがかかります(コンパイラーがすべてをマシンに焼き付けることができる場合)コード)。

マルチスレッドコードでは、コストが高くなります。アトミックなインクリメント/デクリメントまたはロックが必要ですが、どちらもコストがかかる可能性があります。最新のプロセッサーでは、アトミック操作は単純なレジスター操作よりも20倍程度高くなる可能性があります(明らかにプロセッサーごとに異なります)。これにより、コストが増加する可能性があります。

これにより、いくつかのモデルによるトレードオフを検討できます。

  • Objective-CはARC-自動化された参照カウントに焦点を当てています。彼らのアプローチは、すべてに参照カウントを使用することです。 (私が知っている)サイクルの検出はありません。そのため、プログラマーはサイクルの発生を防止することが期待されています。彼らの理論は、ポインタはそれほど頻繁に割り当てられないということであり、それらのコンパイラは、参照カウントのインクリメント/デクリメントによってオブジェクトが死ぬことのない状況を識別し、それらのインクリメント/デクリメントを完全に取り除くことができます。したがって、参照カウントのコストを最小限に抑えます。

  • CPythonはハイブリッドメカニズムを使用します。参照カウントを使用しますが、サイクルを識別して解放するガベージコレクターも備えています。これは、両方のアプローチを犠牲にして、両方の世界の利点を提供します。 CPythonは両方とも参照カウントを維持する必要がありますおよび帳簿管理を行ってサイクルを検出します。 CPythonはこれを2つの方法で回避します。第一に、CPythonは実際には完全にマルチスレッド化されていないということです。マルチスレッドを制限するGILと呼ばれるロックがあります。これは、CPythonがアトミックなものではなく通常の増分/減分を使用できることを意味し、はるかに高速です。 CPythonも解釈されます。つまり、変数への代入などの操作は、1つではなく、すでに少数の命令を必要とします。インクリメント/デクリメントを実行するための追加コストは、Cコードで迅速に実行されます。 veはすでにこの費用を支払いました。

  • Javaは、参照カウントシステムをまったく保証しないアプローチを採用しています。実際、仕様ではanythingは、自動ストレージ管理システムが存在すること以外のオブジェクトの管理方法については述べていません。ただし、この仕様では、これがサイクルを処理する方法でガベージコレクションされることを強く示唆しています。オブジェクトの有効期限を指定しないことで、Javaは、時間の増分/減分を無駄にしないコレクターを使用する自由を獲得します。実際、世代別ガベージコレクターのような賢いアルゴリズムは、見ていなくても多くの単純なケースを処理できます再利用されているデータで(まだ参照されているデータを見るだけでよい)。

したがって、これら3つそれぞれがトレードオフを行わなければならなかったことがわかります。どのトレードオフが最適かは、言語の使用方法の性質に大きく依存します。

5
Cort Ammon

finalizeはJavaのGCにピギーバックされていますが、コアのガベージコレクションは、死んだオブジェクトではなく、生きているオブジェクトに関心があります。一部のGCシステム(Javaのいくつかの実装を含む場合があります)では、オブジェクトを表すビットの束と、何にも使用されていないストレージの束とを区別する唯一のことは、前者への参照が存在することです。ファイナライザを持つオブジェクトは特別なリストに追加されますが、他のオブジェクトは、ユーザーコードに保持されている参照を除いて、ストレージがオブジェクトに関連付けられていることをユニバースのどこにも持たない可能性があります。最後のそのような参照が上書きされると、メモリ内のビットパターンは即時として認識されなくなり、ユニバース内の何かがそのことを認識しているかどうかにかかわらず。

ガベージコレクションの目的は、参照が存在しないオブジェクトを破棄することではなく、次の3つのことを実行することです。

  1. 関連付けられている強い到達可能な参照がないオブジェクトを識別する弱い参照を無効にします。

  2. システムのファイナライザを使用してオブジェクトのリストを検索し、それらに関連付けられた強い到達可能な参照がないオブジェクトがあるかどうかを確認します。

  3. オブジェクトが使用していないストレージ領域を特定して統合します。

GCの主な目標は#3であり、GCを実行するまでの待機時間が長いほど、統合の機会が増えることに注意してください。ストレージをすぐに使用する場合は#3を実行するのが理にかなっていますが、それ以外の場合は延期する方が理にかなっています。

4
supercat

あなたの質問の言い換えと一般化を提案させてください:

JavaがGCプロセスについて強力な保証をしないのはなぜですか?

それを念頭に置いて、ここで答えをすばやくスクロールしてください。これまでに7つあり(これは数えません)、かなりの数のコメントスレッドがあります。

それがあなたの答えです。

GCは難しいです。多くの考慮事項、多くの異なるトレードオフ、そして最終的には、多くの非常に異なるアプローチがあります。これらのアプローチのいくつかは、オブジェクトが不要になったときにすぐにオブジェクトをGCすることを可能にします。他の人はしません。 契約を緩やかに保つことにより、Javaは実装者により多くのオプションを提供します。

もちろん、その決定にもトレードオフがあります。契約を緩やかに保つことにより、Java most *は、プログラマがデストラクタに依存する能力を奪います。これは、C++プログラマが特に頻繁に行うことです。ミス([引用が必要];))なので、取るに足らないトレードオフではありません。私はその特定のメタ決定についての議論を見たことがありませんが、おそらくJava人々は、より多くのGCオプションを持つことの利点が、オブジェクトをいつプログラマーに正確に伝えることができることの利点を上回ると判断しました破壊されます。


* finalizeメソッドがありますが、この回答の範囲外であるさまざまな理由により、それに依存することは困難であり、良い考えではありません。

4
yshavit

開発者が明示的にコードを記述せずにメモリを処理するには、ガベージコレクションと参照カウントという2つの異なる方法があります。

ガベージコレクションには、開発者が愚かなことをしない限り、「機能する」という利点があります。参照カウントを使用すると、参照サイクルを使用できます。つまり、「循環」しますが、開発者は賢い必要がある場合があります。つまり、ガベージコレクションにはプラスです。

参照カウントでは、参照カウントがゼロになると、オブジェクトはすぐに消えます。これは参照カウントの利点です。

スピードワイズでは、ガベージコレクションのファンを信じる場合はガベージコレクションが高速になり、参照カウントのファンを信じる場合は参照カウントが速くなります。

Javaは1つの方法を選択し、Objective-Cは別の方法を選択しました(そして多くのコンパイラサポートを追加して、苦痛から苦痛に変更しました)開発者にとってはほとんど手間がかかりません)。

Javaガベージコレクションから参照カウントに変更することは、多くのコード変更が必要になるため、主要な作業になります。

理論的には、Javaは、ガベージコレクションと参照カウントの混合を実装している可能性があります:参照カウントが0の場合、オブジェクトは到達不可能ですが、必ずしもその逆ではありません。したがって、- could参照カウントを維持し、参照カウントがゼロのときにオブジェクトを削除します(その後、ガベージコレクションを実行して、到達不可能な参照サイクル内でオブジェクトをキャッチします)。参照カウントをガベージコレクションに追加することは悪い考えであり、ガベージコレクションを参照カウントに追加することは悪い考えだと考える人もいるので、これは起こりません。

したがって、Java could参照カウントがゼロになるとすぐにオブジェクトを削除し、後で到達不可能なサイクル内でオブジェクトを削除します。しかし、それは設計上の決定であり、Javaそれに対して決定しました。

3
gnasher729

オブジェクトへの参照がなくなったときの理解の難しさに関する他のすべてのパフォーマンスの議論と議論は正しいですが、言及する価値があると思うもう1つのアイデアは、このようなことを検討する少なくとも1つのJVM(azul)があるということですこれは、本質的にvmスレッドが参照を常にチェックしてそれらを削除しようとする並列gcを実装するという点で、あなたが話していることと完全に異なるわけではありません。基本的にそれは常にヒープを見回して、参照されていないメモリを取り戻そうとします。これにより、ごくわずかなパフォーマンスコストが発生しますが、GC時間は本質的にゼロまたは非常に短くなります。 (つまり、常に拡大するヒープサイズがシステムを超えない限りRAMそしてAzulは混乱し、ドラゴンが存在します)

[〜#〜] tldr [〜#〜] JVMにはそのようなものが存在し、それは特別なjvmであり、他のエンジニアリングの妥協のような欠点があります。

免責事項:私は前の仕事で使用したアズールとは関係がありません。

1
ford prefect

持続スループットの最大化またはGCレイテンシの最小化は動的に緊張しています。これが、GCがすぐに発生しない最も一般的な理由です。 911緊急アプリなど一部のシステムでは、特定のレイテンシしきい値を満たさない場合、サイトのフェイルオーバープロセスがトリガーされる可能性があります。銀行や裁定取引のサイトなど、他のサイトでは、スループットを最大化することがはるかに重要です。

1
barmid

速度

なぜこれがすべて起こっているのかは、最終的には速度のせいです。プロセッサが無限に高速であるか、または(実用的に)それに近い場合。 1秒あたり1,000,000,000,000,000,000,000,000,000,000,000回の操作を行うと、参照解除されたオブジェクトが確実に削除されるなど、各オペレーター間で非常に長く複雑なことが発生する可能性があります。現在、1秒あたりの操作数は正しくなく、他のほとんどの回答がこれを理解することは実際には複雑でリソース集約的であることを説明しているため、ガベージコレクションが存在するため、プログラムは実際に達成しようとしていることに集中できます。スピーディーに。

0
Michael Durrant