少し前に、私は上級 Java 開発エンジニアとしての仕事を探している何人かの候補者と面接しました。私はよく彼らにインタビューして、「Java の弱参照をいくつか紹介してもらえますか?」と尋ねます。もしインタビュアーが「そうですね、それはガベージ コレクションに関連していますか?」と言った場合、私は基本的に満足します。答えが物事の真相に迫る論文の説明であることを期待しないでください。
しかし、予想に反して、平均5年の開発経験と高学歴を持った20人近い候補者のうち、弱参照の存在を知っていたのは2人だけで、しかも実際に知っていたのはその2人のうち1人だけだったのには驚きました。これについて学びましょう。面接の際も、いきなり「これですよ」と言われてしまうのではないかと、いくつか促してみたのですが、結果は非常に残念でした。結局のところ、弱参照は非常に便利な機能であり、この機能は 7 年前に Java 1.2 がリリースされたときに導入されたのです。
この記事を読んだ後、あなたが弱参照の専門家になることは期待していませんが、少なくとも、弱参照とは何か、その使用方法、および弱参照がどのようなシナリオで使用されるかを理解する必要があると思います。あまり知られていない概念なので、前の 3 つの質問について簡単に説明します。
強力な参照
強参照とは私たちがよく使う参考書のことで、次のように書かれています。
次のようにコードをコピーします。
StringBuffer バッファ = new StringBuffer();
上記は StringBuffer オブジェクトを作成し、このオブジェクトへの (強い) 参照を変数バッファーに保存します。はい、これは小児の手術です(こんなことを言うのはご容赦ください)。強い参照について最も重要なことは、参照を強力にすることができ、それによってガベージ コレクターとの相互作用が決まります。具体的には、オブジェクトが強参照リンクの文字列を介して到達可能である場合 (強く到達可能)、そのオブジェクトはリサイクルされません。これは、作業中のオブジェクトをリサイクルしたくない場合にまさに必要なものです。
しかし、強い引用はとても強いです
プログラムでは、クラスを拡張不可能に設定することはやや珍しいことですが、これはクラスを最終クラスとしてマークすることで実現できます。または、未知の数の特定の実装を含むファクトリ メソッドを通じてインターフェイス (インターフェイス) を返す、より複雑な場合もあります。例えば、Widgetというクラスを使いたいのですが、このクラスは継承できないため、新しい機能を追加することができません。
しかし、Widget オブジェクトに関する追加情報を追跡したい場合はどうすればよいでしょうか? 各オブジェクトのシリアル番号を記録する必要があるとします。ただし、Widget クラスにはこの属性が含まれておらず、拡張できないため、この属性を追加できません。実際、HashMap は上記の問題を完全に解決できます。
次のようにコードをコピーします。
serialNumberMap.put(ウィジェット, ウィジェットシリアル番号);
これは表面的には問題ないように見えますが、ウィジェット オブジェクトへの強い参照により問題が発生する可能性があります。ウィジェットのシリアル番号が不要になったら、マップからエントリを削除する必要があると確信できます。削除しないとメモリ リークが発生したり、手動で削除すると使用しているウィジェットが削除されてしまい、有効なデータが失われる可能性があります。実際、これらの問題は非常に似ています。これは、ガベージ コレクション メカニズムを持たない言語がメモリを管理するときによく遭遇する問題です。ただし、ガベージ コレクション メカニズムを備えた Java 言語を使用しているため、この問題について心配する必要はありません。
強参照によって引き起こされる可能性のあるもう 1 つの問題は、特に画像のような大きなファイルの場合、キャッシュです。ユーザーが提供した画像を処理する必要があるプログラムがあるとします。一般的なアプローチは画像データをキャッシュすることです。これは、ディスクから画像をロードするのに非常にコストがかかり、同時に同じ画像データのコピーが 2 つ存在することも避けたいためです。同時に記憶の中に。
キャッシュの目的は、不要なファイルが再び読み込まれるのを防ぐことです。キャッシュには常にメモリ内の画像データへの参照が含まれていることがすぐにわかります。強参照を使用すると、イメージ データがメモリ内に強制的に保持されるため、イメージ データが不要になった時期を判断し、ガベージ コレクターによって再利用できるようにキャッシュから手動で削除する必要があります。したがって、再びガベージ コレクターの動作を実行し、どのオブジェクトをクリーンアップするかを手動で決定する必要があります。
弱参照
弱い参照とは、オブジェクトをメモリ内に保持することがそれほど強力ではない参照のことです。 WeakReference を使用すると、ガベージ コレクターは、参照されたオブジェクトがいつリサイクルされるかを決定し、メモリからオブジェクトを削除するのに役立ちます。次のように弱い参照を作成します。
次のようにコードをコピーします。
eakReference<Widget>weakWidget = new WeakReference<Widget>(ウィジェット);
実際の Widget オブジェクトは、weakWidget.get() を使用して取得できます。弱い参照では、ガベージ コレクターによる再利用を防ぐことができないため、(ウィジェット オブジェクトへの強い参照がない場合) を使用すると、突然 null が返されることがわかります。得る。
ウィジェットのシーケンス番号記録に関する上記の問題を解決する最も簡単な方法は、Java の組み込み WeakHashMap クラスを使用することです。 WeakHashMap は HashMap とほぼ同じですが、唯一の違いは、キー (値ではなく!!!) が WeakReference によって参照されることです。 WeakHashMap キーがガベージとしてマークされると、このキーに対応するエントリは自動的に削除されます。これにより、不要な Widget オブジェクトを手動で削除するという上記の問題が回避されます。 WeakHashMap は、HashMap または Map に簡単に変換できます。
参照キュー
弱参照オブジェクトが null を返し始めると、その弱参照が指すオブジェクトはガベージとしてマークされます。そして、この弱参照オブジェクト (それが指すオブジェクトではない) は役に立ちません。通常、この時点で何らかのクリーンアップを行う必要があります。たとえば、WeakHashMap は、無限に増大する無意味な弱参照の保存を避けるために、この時点で不要なエントリを削除します。
参照キューを使用すると、不要な参照を簡単に追跡できます。 WeakReference を構築するときに ReferenceQueue オブジェクトを渡すと、参照が指すオブジェクトがガベージとしてマークされると、参照オブジェクトは参照キューに自動的に追加されます。次に、これらの役に立たない参照オブジェクトを処理するクリーンアップ作業を行うなど、受信した参照キューを一定の周期で処理できます。
4種類の参考書
Java には、強参照、ソフト参照、弱参照、仮想参照という、強さの異なる参照の 4 種類があります。上のセクションでは強参照と弱参照を紹介し、残りの 2 つであるソフト参照と仮想参照について説明します。
ソフトリファレンス
ソフト参照は基本的に弱参照と同じですが、ガベージ コレクション期間がそれが指すオブジェクトをリサイクルするのを防ぐ能力が弱参照よりも強力である点が異なります。オブジェクトが弱い参照によって到達可能な場合、そのオブジェクトは次のコレクション サイクルでガベージ コレクターによって破棄されます。ただし、ソフト参照に到達できる場合、オブジェクトはメモリ内に長時間留まります。ガベージ コレクターは、メモリが不足している場合にのみ、これらのソフト参照によって到達可能なオブジェクトを再利用します。
ソフト参照によって到達可能なオブジェクトは、弱い参照によって到達可能なオブジェクトよりも長くメモリ内に留まるため、この機能をキャッシュに使用できます。このようにして、ガベージ コレクターは、現在どの型に到達できるか、および処理のためのメモリ消費の程度を考慮します。
ファントムリファレンス
ソフト参照や弱参照とは異なり、仮想参照が指すオブジェクトは非常に壊れやすいため、get メソッドを使用して仮想参照が指すオブジェクトを取得することはできません。その唯一の機能は、それが指すオブジェクトがリサイクルされた後、参照が指すオブジェクトが破棄されたことを記録するために参照キューに追加されることです。
弱参照が指すオブジェクトが弱参照によって到達可能になると、弱参照は参照キューに追加されます。この操作は、オブジェクトの破棄またはガベージ コレクションが実際に発生する前に発生します。理論的には、リサイクルされようとしているオブジェクトは、非準拠のデストラクター メソッドで復活させることができます。ただし、この弱い参照は破棄されます。仮想参照は、それが指すオブジェクトがメモリから削除された後にのみ参照キューに追加されます。その get メソッドは、それが指すほとんど破壊されたオブジェクトが復活するのを防ぐために null を返し続けます。
仮想参照には主に 2 つの使用シナリオがあります。これにより、参照するオブジェクトがいつメモリから削除されるかを正確に知ることができます。そして実際、これが Java における唯一の方法です。これは、画像などの大きなファイルを扱う場合に特に当てはまります。イメージ データ オブジェクトをリサイクルする必要があると判断した場合は、次のイメージの読み込みを続行する前に、仮想参照を使用してオブジェクトがリサイクルされるかどうかを判断できます。こうすることで、ひどいメモリ オーバーフロー エラーを可能な限り回避できます。
2 番目の点は、仮想参照により、破棄中の多くの問題を回避できるということです。 Finalize メソッドは、破棄されようとしているオブジェクトを指す強参照を作成することで、それらのオブジェクトを復活させることができます。ただし、finalize メソッドをオーバーライドするオブジェクトをリサイクルするには、2 つの別々のガベージ コレクション サイクルを通過する必要があります。最初のサイクルでは、オブジェクトはリサイクル可能としてマークされ、その後破棄できるようになります。しかし、このオブジェクトが破壊の過程で復活する可能性がわずかに残っているためです。この場合、オブジェクトが実際に破棄される前に、ガベージ コレクターを再度実行する必要があります。破棄はあまり適時に行われない可能性があるため、オブジェクトの破棄が呼び出される前に、不定の数のガベージ コレクション サイクルを実行する必要があります。これは、実際にオブジェクトをクリーンアップする際に大幅な遅延が発生する可能性があることを意味します。ヒープの大部分がガベージとしてマークされている場合でも、迷惑なメモリ不足エラーが発生するのはこのためです。
仮想参照を使用すると、仮想参照が参照キューに追加されると、破棄されたオブジェクトを取得する方法がまったくなくなります。なぜなら、この時点でオブジェクトは記憶から消去されているからです。仮想参照を使用してそれが指すオブジェクトを再生成することはできないため、そのオブジェクトはガベージ コレクションの最初のサイクルでクリーンアップされます。
明らかに、finalize メソッドをオーバーライドすることはお勧めできません。仮想参照は明らかに安全で効率的であるため、finalize メソッドを削除すると仮想マシンが大幅に簡素化されます。もちろん、このメソッドをオーバーライドしてさらに多くのことを実現することもできます。それはすべて個人の選択次第です。
要約する
これを見て、なぜ過去 10 年間の古い API について話しているのか、多くの人が不満を持ち始めていると思います。私の経験からすると、多くの Java プログラマーはこの知識をよく知らないと思います。 -深い理解が必要です。この記事から皆さんが何かを得られることを願っています。