Javaでは、オブジェクトに参照がなくなるとすぐに削除の対象になりますが、JVMはオブジェクトが実際に削除されるタイミングを決定します。Objective-Cの用語を使用する場合、すべてのJava参照は本質的に「強力」です。ただし、Objective-Cでは、オブジェクトに強参照がなくなった場合、オブジェクトはすぐに削除されます。なぜこれがJavaに当てはまらないのですか?
Javaでは、オブジェクトに参照がなくなるとすぐに削除の対象になりますが、JVMはオブジェクトが実際に削除されるタイミングを決定します。Objective-Cの用語を使用する場合、すべてのJava参照は本質的に「強力」です。ただし、Objective-Cでは、オブジェクトに強参照がなくなった場合、オブジェクトはすぐに削除されます。なぜこれがJavaに当てはまらないのですか?
回答:
まず、Javaには弱い参照と、ソフト参照と呼ばれる別のベストエフォートカテゴリがあります。弱い参照と強い参照は、参照カウントとガベージコレクションとはまったく別の問題です。
第二に、メモリ使用量のパターンがあり、スペースを犠牲にすることで、ガベージコレクションをより効率的にすることができます。たとえば、新しいオブジェクトは、古いオブジェクトよりも削除される可能性がはるかに高くなります。したがって、スイープの間に少し待つと、少数の生存者をより長期のストレージに移動しながら、新しい世代のメモリのほとんどを削除できます。その長期保存は、はるかに少ない頻度でスキャンできます。手動のメモリ管理または参照カウントを介した即時削除は、断片化を起こしやすい傾向があります。
これは、給料ごとに食料品の買い物に行くことと、一日に十分な食料を得るために毎日行くこととの違いのようなものです。1回の大きな旅行は、個々の小さな旅行よりもはるかに時間がかかりますが、全体的には時間とお金を節約できます。
何かを適切に知ることはもはや参照されていないため、簡単ではありません。簡単にも近い。
相互に参照する2つのオブジェクトがある場合はどうなりますか?彼らは永遠に滞在しますか?この考え方を拡張して任意のデータ構造を解決すると、JVMやその他のガベージコレクターが、まだ必要なものと何ができるかを判断するためのはるかに洗練された方法を採用せざるを得ない理由がすぐにわかります。
私の知る限り、JVM仕様(英語で書かれています)では、オブジェクト(または値)を正確にいつ削除するかについて言及せず、それを実装に残しています(R5RSの場合も同様)。何らかの方法でガベージコレクタが必要または提案されますが、詳細は実装に任せます。また、Java仕様についても同様です。
プログラミング言語は、ソフトウェアの実装ではなく、(構文、セマンティクスなどの)仕様であることに注意してください。Java(またはそのJVM)のような言語には多くの実装があります。その仕様は公開されており、ダウンロード可能(学習できるように)され、英語で書かれています。§2.5.3JVM仕様のヒープには、ガベージコレクターが記載されています。
オブジェクトのヒープストレージは、自動ストレージ管理システム(ガベージコレクター)によって回収されます。オブジェクトが明示的に割り当て解除されることはありません。Java仮想マシンは、特定のタイプの自動ストレージ管理システムを想定していません
(強調は私です。BTWファイナライズは、に記載されている§12.6のJava仕様の、およびメモリモデルがである§17.4のJava仕様の)
そのため(Javaでは)オブジェクトがいつ削除されるかを気にする必要はありません。また、(それを無視する抽象化で推論することにより)発生しない場合はコーディングできます。もちろん、メモリ消費と生きているオブジェクトのセットに注意する必要がありますが、これは別の質問です。いくつかの単純なケース(「hello world」プログラムのような)では、割り当てられたメモリがかなり小さい(たとえば1ギガバイト未満)ことを証明することができます-または自分自身を納得させることができます。個々のオブジェクトの削除。多くの場合、生き物は(または到達可能なもの、これは生きているもののスーパーセット-推論するのが簡単です-)合理的な制限を超えることはありません(そして、GCに依存しますが、ガベージコレクションがいつどのように発生するかは気にしません)。スペースの複雑さについて読んでください。
Hello Worldのような短命のJavaプログラムを実行しているいくつかのJVM実装では、ガベージコレクターはまったくトリガーされず、削除は行われないと思います。AFAIU、そのような振る舞いは多数のJava仕様に準拠しています。
ほとんどのJVM実装は世代別のコピー技術を使用します(少なくともほとんどのJavaオブジェクトでは、ファイナライズまたはウィークリファレンスを使用しません;ファイナライズは短時間で発生することが保証されておらず、延期される可能性があるため、コードがすべきではない便利な機能です(個々のオブジェクトを削除するという概念が意味をなさない)(多くのオブジェクトのメモリゾーンを含む多くのメモリブロック、おそらく一度に数メガバイト)が一度に解放されるため)
JVM仕様で各オブジェクトをできるだけ正確に削除する必要がある場合(または単にオブジェクトの削除により多くの制約を課す場合)、効率的な世代別GCテクニックは禁止され、JavaおよびJVMの設計者はそれを避けるのが賢明です。
ところで、オブジェクトを決して削除せず、メモリを解放しないナイーブJVMは、仕様(精神ではなく文字)に準拠している可能性があり、実際にハローワールドを実際に実行できる可能性があります(ほとんどのことに注意してください)小さくて短命なJavaプログラムは、おそらく数ギガバイト以上のメモリを割り当てません。もちろん、このようなJVMは言及する価値はなく、単なるおもちゃです(このmalloc
Cの実装のように)。詳細については、Epsilon NoOp GCを参照してください。実際のJVMは非常に複雑なソフトウェアであり、いくつかのガベージコレクション技術が混在しています。
また、JavaはJVMとは異なり、JVMなしで実行するJava実装があります(たとえば、事前の Javaコンパイラー、Androidランタイム)。では、いくつかの例(主に学術的なもの)、あなたが想像するかもしれない(そう、「コンパイル時のガベージコレクション」技術と呼ばれる)は、Javaプログラムが(実行時に割り当てたり削除しないことを例えばので、最適化コンパイラは、のみを使用してくれてきました呼び出しスタックと自動変数)。
Javaオブジェクトが参照されなくなった直後に削除されないのはなぜですか?
JavaとJVMの仕様はそれを必要としないためです。
詳細については、GCハンドブック(およびJVM仕様)を参照してください。オブジェクトの生存(または将来の計算に役立つ)は、プログラム全体(非モジュラー)のプロパティであることに注意してください。
Objective-Cは、メモリ管理への参照カウントアプローチを好みます。また、落とし穴もあります(たとえば、Objective-C プログラマーは、弱い参照を明示することで循環参照に注意する必要がありますが、JVMは実際にはJavaプログラマーの注意を必要とせずに循環参照をうまく処理します)。
プログラミングとプログラミング言語の設計にはSilver Bulletはありません(Haltting Problemに注意してください;有用な生きているオブジェクトであることは、一般的に決定できません)。
また、SICP、プログラミング言語プラグマティクス、ドラゴンブック、Lisp In Small PiecesおよびOperating Systems:Three Easy Piecesも読んでください。Javaに関するものではありませんが、心を開き、JVMが何をすべきか、コンピューターで(他の部分と)実際に動作する方法を理解するのに役立ちます。また、既存のオープンソース JVM実装の複雑なソースコード(数百万のソースコード行があるOpenJDKなど)の研究に何ヶ月(または数年)費やすこともできます。
finalize
リソース管理(ファイルハンドル、db接続、gpuリソースなど)に依存できないことを知っておく必要があります。
Objective-Cの用語を使用する場合、すべてのJava参照は本質的に「強力」です。
これは正しくありません-Javaには弱参照とソフト参照の両方がありますが、これらは言語キーワードとしてではなくオブジェクトレベルで実装されます。
Objective-Cでは、オブジェクトに強参照がなくなった場合、オブジェクトはすぐに削除されます。
これも必ずしも正しいとは限りません。ObjectiveCの一部のバージョンでは、実際に世代別のガベージコレクターが使用されていました。他のバージョンにはガベージコレクションがまったくありませんでした。
Objective Cの新しいバージョンでは、トレースベースのGCではなく自動参照カウント(ARC)を使用しているため、参照カウントが0になるとオブジェクトが「削除」されることがよくあります。ただし、JVM実装も準拠しており、まさにこの方法で動作する可能性があることに注意してください(つまり、準拠していて、GCがまったくない場合もあります)。
では、ほとんどのJVM実装がこれを行わず、代わりにトレースベースのGCアルゴリズムを使用するのはなぜですか?
簡単に言えば、ARCは最初に思われるほど理想的ではありません。
ARCにはもちろん利点があります-ARCの実装と収集は簡単です しかし、上記の欠点は、とりわけ、JVM実装の大半が世代ベースのトレースベースのGCを使用する理由です。
Javaは、オブジェクトがガベージコレクションを処理する方法を選択する自由を実装に与えるため、オブジェクトが収集されるタイミングを正確に指定しません。
ガベージコレクションメカニズムにはさまざまなものがありますが、オブジェクトをすぐに収集できることを保証するものは、ほぼ完全に参照カウントに基づいています(この傾向を破るアルゴリズムは知りません)。参照カウントは強力なツールですが、参照カウントを維持するにはコストがかかります。シングルスレッドコードでは、それはインクリメントとデクリメントに過ぎないため、ポインターを割り当てると、参照カウントコードの場合と比較して非参照カウントコードの場合の3倍のコストがかかります(コンパイラーがすべてをマシンに焼き付けることができる場合)コード)。
マルチスレッドコードでは、コストが高くなります。アトミックなインクリメント/デクリメントまたはロックのいずれかを呼び出しますが、どちらも高価になる可能性があります。最新のプロセッサーでは、アトミック操作は単純なレジスター操作よりも20倍程度高価になる場合があります(プロセッサーごとに明らかに異なります)。これにより、コストが増加する可能性があります。
そのため、これにより、いくつかのモデルのトレードオフを考慮することができます。
Objective-Cは、ARC-自動化された参照カウントに焦点を当てています。彼らのアプローチは、すべてに対して参照カウントを使用することです。(私が知っている)サイクル検出はないため、プログラマーは、サイクルの発生を防ぐことが期待されており、開発時間のコストがかかります。彼らの理論では、ポインタはそれほど頻繁には割り当てられず、コンパイラは参照カウントをインクリメント/デクリメントしてもオブジェクトが死なない状況を特定し、それらのインクリメント/デクリメントを完全に排除できます。したがって、参照カウントのコストを最小限に抑えます。
CPythonはハイブリッドメカニズムを使用します。参照カウントを使用しますが、サイクルを識別して解放するガベージコレクタもあります。これは、両方のアプローチの犠牲を払って、両方の世界の利点を提供します。CPythonは参照カウントとサイクルを検出するためにブックキーピングを行います。CPythonはこれを2つの方法で回避します。第一に、CPythonは実際には完全にマルチスレッド化されていないということです。マルチスレッドを制限するGILとして知られるロックがあります。これは、CPythonがアトミックなものではなく通常のインクリメント/デクリメントを使用できることを意味します。これははるかに高速です。CPythonも解釈されます。つまり、変数への代入などの操作は、1つだけではなく、既にいくつかの命令を取ります。 veはすでにこの費用を支払っています。
Javaは、参照カウントシステムをまったく保証しないというアプローチを取り下げます。実際、仕様では、自動ストレージ管理システムがあること以外、オブジェクトの管理方法については何も言及していません。ただし、この仕様は、これがサイクルを処理する方法でガベージコレクションされるという仮定を強く示唆しています。オブジェクトの有効期限を指定しないことにより、javaは、時間の増分/減分を無駄にしないコレクターを自由に使用できます。確かに、世代別ガベージコレクターなどの賢いアルゴリズムは、再生中のデータを見なくても多くの単純なケースを処理することさえできます(参照されているデータのみを見る必要があります)。
したがって、これら3つはそれぞれトレードオフを行う必要がありました。どのトレードオフが最適かは、言語の使用方法の性質に大きく依存します。
finalize
JavaのGCに便乗しましたが、コアのガベージコレクションは死んだオブジェクトではなく、生きているオブジェクトに関心があります。一部のGCシステム(Javaの一部の実装を含む可能性があります)では、オブジェクトを表すビットの集合と、何にも使用されないストレージの集合を区別する唯一のものは、前者への参照の存在です。ファイナライザを持つオブジェクトは特別なリストに追加されますが、他のオブジェクトは、ユーザーコードに保持されている参照を除き、ストレージがオブジェクトに関連付けられているというユニバースのどこにも存在しない場合があります。このような最後の参照が上書きされると、メモリ内のビットパターンは、宇宙の何かがそれを認識しているかどうかに関係なく、オブジェクトとしてすぐに認識できなくなります。
ガベージコレクションの目的は、参照が存在しないオブジェクトを破棄することではなく、次の3つのことを達成することです。
強到達可能な参照が関連付けられていないオブジェクトを識別する弱参照を無効にします。
ファイナライザを使用してシステムのオブジェクトのリストを検索し、それらに関連付けられた強力に到達可能な参照がないオブジェクトがあるかどうかを確認します。
オブジェクトによって使用されていないストレージの領域を特定して統合します。
GCの主な目標は3番目であり、GCを実行するまでの待機時間が長いほど、統合の機会が増える可能性が高いことに注意してください。ストレージをすぐに使用する場合は#3を行うのが理にかなっていますが、そうでない場合はそれを延期する方が理にかなっています。
あなたの質問の言い換えと一般化を提案させてください。
JavaがGCプロセスについて強力な保証をしないのはなぜですか?
それを念頭に置いて、ここで回答をすばやくスクロールしてください。これまでに7つあり(これは数えていません)、コメントスレッドはかなりあります。
それがあなたの答えです。
GCは難しいです。多くの考慮事項、多くの異なるトレードオフ、そして最終的には非常に異なるアプローチがたくさんあります。これらのアプローチのいくつかは、オブジェクトが不要になるとすぐにGCを実行できるようにします。他の人はしません。契約を緩く保つことにより、Javaは実装者により多くのオプションを提供します。
もちろん、その決定にもトレードオフがあります。契約を緩く保つことにより、Java *はほとんどプログラマーがデストラクタに依存する能力を奪います。これは特にC ++プログラマが見逃しがちなものです([要出典];))。したがって、取るに足らないトレードオフではありません。私はその特定のメタ決定についての議論を見ていませんが、おそらく、Javaの人々は、GCオプションを増やすことの利点は、オブジェクトがいつ破壊されるかをプログラマに正確に伝えることができるという利点を上回ると判断したと思われます。
* finalize
方法はありますが、この答えの範囲外のさまざまな理由から、それに頼るのは難しく、良い考えではありません。
開発者が明示的なコードを記述せずにメモリを処理するには、ガベージコレクションと参照カウントの2つの異なる戦略があります。
ガベージコレクションには、開発者が愚かなことをしない限り「機能する」という利点があります。参照カウントを使用すると、参照サイクルを設定できます。つまり、「機能」しますが、開発者は賢明な場合があります。ガベージコレクションにはプラスになります。
参照カウントでは、参照カウントがゼロになるとオブジェクトはすぐに消えます。これは、参照カウントの利点です。
速度面では、ガベージコレクションのファンを信じる場合はガベージコレクションが速くなり、参照カウントのファンを信じる場合は参照カウントが速くなります。
同じ目標を達成するための2つの異なる方法であり、Javaが1つの方法を選択し、Objective-Cが別の方法を選択しました(さらに、多くのコンパイラーサポートを追加して、手間のかかるものから開発者にとってほとんど役に立たないものに変更しました)。
Javaをガベージコレクションから参照カウントに変更することは、多くのコード変更が必要になるため、大きな仕事です。
理論的には、Javaはガベージコレクションと参照カウントの混合を実装できます。参照カウントが0の場合、オブジェクトは到達不能ですが、必ずしもその逆ではありません。そのため、参照カウントを保持し、参照カウントがゼロのときにオブジェクトを削除することができます(そして、ガベージコレクションを時々実行して、到達不能な参照サイクル内でオブジェクトをキャッチします)。ガベージコレクションに参照カウントを追加するのは悪い考えだと思う人と、参照カウントにガベージコレクションを追加するのは悪い考えだと思う人とでは、世界は50対50に分かれていると思います。したがって、これは起こりません。
そのため、Java は、参照カウントがゼロになるとすぐにオブジェクトを削除し、到達不能なサイクル内でオブジェクトを削除できます。しかし、それは設計上の決定であり、Javaはそれに対して決定しました。
私が言及する価値があると思うもう1つのアイデアは、このようなものを検討する少なくとも1つのJVM(azul)があるということですがそれは、基本的にvmスレッドが常に参照をチェックしてそれらを削除しようとする並列gcを実装していることであり、これはあなたが話していることとはまったく異なりません。基本的には、ヒープを常に見回し、参照されていないメモリを回収しようとします。これにより、非常にわずかなパフォーマンスコストが発生しますが、本質的にゼロまたは非常に短いGC時間につながります。(つまり、常に拡大するヒープサイズがシステムRAMを超えない限り、Azulは混乱し、ドラゴンが存在します)
TLDR JVMには、そのようなものが存在します。これは特別なjvmであり、他のエンジニアリング上の妥協のような欠点があります。
免責事項:前の仕事で使用したばかりのAzulとは関係がありません。
このすべてが行われている理由は、最終的には速度のためです。プロセッサが無限に高速である場合、または(実用的には)1秒あたり1,000,000,000,000,000,000,000,000,000,000,000操作に近い場合、参照解除されたオブジェクトが削除されていることを確認するなど、非常に長く複雑な処理が各演算子間で発生する可能性があります。現在、1秒あたりの操作数は真実ではなく、他のほとんどの回答が説明しているように、これを理解するのは実際には複雑でリソース集約的であるため、ガベージコレクションが存在するため、プログラムは、迅速な方法。