なぜ明示的に Finalize() を呼び出すか、ガベージ コレクターを開始しないのですか?
-
09-06-2019 - |
質問
読んだあと この質問, 私が Java を教えられたときに、「それは決して心配する必要のない大きなブラックボックスであるため、finalize() を呼び出したり、ガベージ コレクターを実行したりしないでください」と言われたときのことを思い出しました。この理由をいくつかの文に要約できる人はいますか?この問題については Sun の技術レポートを読むことができると思いますが、簡潔で簡潔な答えが私の好奇心を満たしてくれると思います。
解決
短い答え:Java ガベージ コレクションは、非常に細かく調整されたツールです。System.gc() は大ハンマーです。
Java のヒープはさまざまな世代に分割されており、それぞれが異なる戦略を使用して収集されます。健全なアプリにプロファイラーをアタッチすると、ほとんどのオブジェクトが若い世代の高速コピー コレクターによってキャッチされるため、最も高価な種類のコレクションを実行する必要がほとんどないことがわかります。
System.gc() を直接呼び出すと、技術的には何も実行されることが保証されていませんが、実際には、コストがかかる、ストップザワールドのフル ヒープ コレクションがトリガーされます。これは ほとんど常に間違ったことをする. 。リソースを節約しているつもりですが、実際には正当な理由もなくリソースを無駄にしているため、Java は「万が一に備えて」すべてのライブ オブジェクトを再チェックする必要があります。
重大な瞬間に GC が一時停止するという問題が発生している場合は、問題を解決するために大槌で対処するよりも、同時マーク/スイープ コレクターを使用するように JVM を構成する方が良いでしょう。これは、一時停止にかかる時間を最小限に抑えるように特別に設計されています。それをさらに壊す。
あなたが考えていた Sun の文書はここにあります。 Java SE 6 HotSpot™ 仮想マシンのガベージ コレクションのチューニング
(あなたが知らないかもしれないもう一つのこと:オブジェクトに Finalize() メソッドを実装すると、ガベージ コレクションが遅くなります。まず、かかります 二 GC が実行されてオブジェクトが収集されます。1 つは Finalize() を実行するため、もう 1 つはファイナライズ中にオブジェクトが復活しなかったことを確認するためです。次に、finalize() メソッドを持つオブジェクトは、個別に収集する必要があり、一括して破棄することはできないため、GC によって特殊なケースとして扱われる必要があります。)
他のヒント
ファイナライザーについては気にしないでください。
増分ガベージ コレクションに切り替えます。
ガベージ コレクターを支援したい場合は、不要になったオブジェクトへの参照を無効にします。たどるパスが少なくなる = より明確にゴミになります。
(非静的) 内部クラス インスタンスは親クラス インスタンスへの参照を保持することを忘れないでください。したがって、内部クラスのスレッドは、予想よりもはるかに多くの荷物を保持します。
これと非常に関連した話ですが、シリアル化を使用していて、一時オブジェクトをシリアル化した場合は、ObjectOutputStream.reset() を呼び出してシリアル化キャッシュをクリアする必要があります。そうしないと、プロセスがメモリ リークを起こし、最終的には終了します。欠点は、非一時オブジェクトが再シリアル化されることです。一時結果オブジェクトのシリアル化は、思っているよりも少し面倒な場合があります。
ソフト参照の使用を検討してください。ソフト参照が何なのかわからない場合は、java.lang.ref.SoftReference の javadoc を読んでください。
本当に興奮しない限り、ファントム参照や弱い参照は避けてください。
最後に、GC がどうしても許容できない場合は、Realtime Java を使用してください。
いいえ、冗談ではありません。
リファレンス実装は無料でダウンロードでき、SUN の Peter Dibbles の本は非常に読み物です。
ファイナライザに関する限り:
- それらは事実上役に立ちません。これらはタイムリーに呼び出されるという保証はなく、実際にはまったく呼び出されることは保証されていません (GC が実行されない場合、ファイナライザーも実行されません)。これは、通常はそれらに依存すべきではないことを意味します。
- ファイナライザは冪等であることが保証されていません。ガベージ コレクターは、決して呼び出されないことを保証するために細心の注意を払っています。
finalize()
同じオブジェクトに対して複数回。オブジェクトが適切に作成されている場合は問題になりませんが、オブジェクトが適切に作成されていない場合は、finalize を複数回呼び出すと問題が発生する可能性があります (例:ネイティブ リソースの二重リリース ...クラッシュ)。 - 毎 を持つオブジェクト
finalize()
メソッドも提供する必要がありますclose()
(または同様の)方法。これは呼び出す必要がある関数です。例えば。、FileInputStream.close()
. 。電話する理由がないfinalize()
もっと適切な方法があるときは、 は あなたから電話を受けることを目的としています。
ファイナライザーが .NET の名前に似ていると仮定すると、実際にこれらを呼び出す必要があるのは、ファイル ハンドルなどのリークの可能性のあるリソースがある場合だけです。ほとんどの場合、オブジェクトにはこれらの参照がないため、呼び出す必要はありません。
本当は自分のゴミではないのに、ゴミを集めようとするのは良くありません。オブジェクトの作成時に VM にメモリを割り当てるように指示しましたが、ガベージ コレクターはそれらのオブジェクトに関する情報を隠しています。内部的には、GC はメモリ割り当ての最適化を実行します。ゴミを手動で収集しようとすると、GC が何を保持して削除したいのかがわかりません。単に強制的に手を加えているだけです。その結果、内部計算が台無しになります。
GC が内部的に保持しているものについてもっと知っていれば、より多くの情報に基づいた意思決定ができるかもしれませんが、それでは GC の利点を逃していることになります。
ファイナライズ中に OS ハンドルを閉じる場合の本当の問題は、ファイナライズが保証された順序で実行されないことです。しかし、ブロックするものへのハンドルがある場合(たとえば、ソケット) 潜在的にコードがデッドロック状態に陥る可能性があります (決して簡単ではありません)。
したがって、私は、ハンドルを予測可能な順序で明示的に閉じることに賛成です。基本的に、リソースを処理するコードは次のパターンに従う必要があります。
SomeStream s = null;
...
try{
s = openStream();
....
s.io();
...
} finally {
if (s != null) {
s.close();
s = null;
}
}
JNI を介して動作し、ハンドルを開く独自のクラスを作成する場合は、さらに複雑になります。ハンドルが閉じられる (解放される) こと、およびそれが 1 回だけ行われることを確認する必要があります。Desktop J2SE で見落とされがちな OS ハンドルは次のとおりです。 Graphics[2D]
. 。平 BufferedImage.getGrpahics()
ビデオ ドライバー (実際には GPU 上のリソースを保持している) を指すハンドルを返す可能性があります。自分でリリースせず、ガベージ コレクターに作業を任せた場合、ビデオ カードにマップされたビットマップが不足しているにもかかわらずメモリがまだ十分にある場合に、奇妙な OutOfMemory や同様の状況が発生する可能性があります。私の経験では、グラフィックス オブジェクト (サムネイルの抽出、拡大縮小、鮮明化など) を扱うタイトなループでこの問題がかなり頻繁に発生します。
基本的に GC は、正しいリソース管理というプログラマーの責任を負いません。メモリのみを処理し、他には何も処理しません。close() を呼び出す Stream.finalize は、例外 new RuntimeError("まだ開いているストリームを収集しているガベージ") をスローするように実装する方がよいでしょう。これにより、ずさんな素人が最終的に負けた後にコードをデバッグしたりクリーニングしたりする何時間も何日も節約できます。
コーディングを楽しんでください。
平和。
GC は、処理を適切に完了するタイミングについて多くの最適化を行います。
そのため、GC が実際にどのように動作するのか、また、GC がどのように世代にタグを付けるのかに精通していない限り、手動で Finalize または Start GC を呼び出すと、おそらく効果が得られるどころか、パフォーマンスが低下するでしょう。
ファイナライザは避けてください。タイムリーに呼び出されるという保証はありません。メモリ管理システム (つまり、ガベージ コレクター) がファイナライザーを使用してオブジェクトを収集することを決定するまでに、かなり長い時間がかかる可能性があります。
多くの人は、ソケット接続を閉じたり、一時ファイルを削除したりするためにファイナライザーを使用します。そうすることで、アプリケーションの動作が予測不能になり、JVM がオブジェクトをいつ GC するかに関連付けられるようになります。これは、Java ヒープが使い果たされるのではなく、システムが特定のリソースのハンドルを使い果たすことが原因で、「メモリ不足」シナリオにつながる可能性があります。
もう 1 つ留意すべき点は、System.gc() またはそのようなハンマーへの呼び出しを導入すると、現在の環境では良い結果が得られる可能性がありますが、必ずしも他のシステムに変換されるとは限らないということです。誰もが同じ JVM を実行しているわけではありません。SUN、IBM J9、BEA JRockit、Harmony、OpenJDK など、たくさんあります。この JVM はすべて JCK (つまり、公式にテストされたもの) に準拠していますが、高速化に関しては多くの自由があります。GC は、誰もが多額の投資を行っている分野の 1 つです。ハンマーを使用すると、多くの場合、その努力が台無しになってしまいます。