java.lang.ref パッケージ (SoftReference、WeakReference、およびPhantomReference クラスが含まれる) がJava 2プラットフォームの一部として初めて導入されたとき、その有用性について過剰宣伝されたといえるでしょう。パッケージに含まれるクラスは有効かもしれませんが、一定の制限があるために、これらのクラスの魅力が乏しくなり、その適用を限定された問題に狭めました。
参照クラスの主な機能は、ガーベッジ・コレクターによってまだ再利用できる状態にあるオブジェクトを参照できることです。参照クラスが導入されるまでは、強参照しか利用できませんでした。たとえば、以下のコード行は、obj という強参照を示しています。
Object obj = new Object(); |
obj 参照は、ヒープに保存されているオブジェクトを参照します。obj 参照が存在する限り、ガーベッジ・コレクターは、オブジェクトを保持するために使用されているストレージを解放しません。
obj が有効なスコープから外れたり、明示的にnull に割り当てられると、オブジェクトは、それへの参照が他にないとすると、回収できるようになります。ただし、ここで特に注意すべき点は、オブジェクトが回収できる状態になっているというだけで、ガーベッジ・コレクターが動作して再利用できる状態になっているわけではないということです。ガーベッジ・コレクション・アルゴリズムにはさまざまなものがあり、より古く、長期間使用されたオブジェクトについては短期のオブジェクトほど頻繁に分析しないアルゴリズムもあります。したがって、回収できる状態にあるオブジェクトは、全く再利用されない可能性があります。このことは、ガーベッジ・コレクターがオブジェクトを解放する前にプログラムが終了するような場合に起こる可能性があります。要するに、回収可能なオブジェクトがガーベッジ・コレクターによって必ず回収されるという保証は全くないということです。
この情報は、参照クラスを分析する際に重要になります。ガーベッジ・コレクションの性質上、これらのクラスは当初考えていたほど有効ではないかもしれませんが、特定の問題に対しては有効なクラスといえます。ソフト参照オブジェクト、弱参照オブジェクト、ファントム参照オブジェクトは、それぞれ異なる方法で、回収を妨げることなくヒープ・オブジェクトを参照します。各参照オブジェクトにはそれぞれの振る舞いがあり、ガーベッジ・コレクターとの相互作用も異なります。また、新しい参照クラスはすべて、典型的な強参照よりも「弱い」タイプの参照を表します。さらに、メモリー内のオブジェクトは、強、ソフト、弱、ファントムの複数の参照によって参照することができます。先に進む前に、いくつかの用語について説明します。
- 強可到達: 強参照によってアクセスできるオブジェクト。
- ソフト可到達: 強可到達ではなく、ソフト参照によってアクセスできるオブジェクト。
- 弱可到達: 強可到達またはソフト可到達ではなく、弱参照によってアクセスできるオブジェクト。
- ファントム可到達: 強可到達、ソフト可到達、または弱可到達ではなく、ファントム参照によってアクセスできる、ファイナライズされたオブジェクト。
-
クリア: 参照オブジェクトの対象フィールド (referent field) を
nullに設定し、参照クラスが参照したヒープ内のオブジェクトをfinalizable として宣言すること。
SoftReference クラスの典型的な使用例は、メモリー・センシティブ・キャッシュです。SoftReference の概念は、JVMがメモリー不足状態を報告する前に、ソフト参照のすべてがクリアされることを保証された状態で、オブジェクトの参照を維持することです。重要な点は、ガーベッジ・コレクターの実行中は、ソフト可到達オブジェクトを解放する可能性も解放しない可能性もあるということです。オブジェクトが解放されるかどうかは、ガーベッジ・コレクターのアルゴリズムと、コレクターの実行中に使用可能なメモリーの量によって決まります。
WeakReference クラスの典型的な使用例は、canonicalized mappingです。また、弱参照は、この仕組みがなければ長期にわたって存在し、再作成のコストもそれほどかからないオブジェクトに使うと役立ちます。重要な点は、ガーベッジ・コレクターの実行中に、弱可到達オブジェクトが発生した場合、WeakReference が参照するオブジェクトをガーベッジ・コレクターが解放することです。ただし、弱可到達オブジェクトを発見して解放する前に、ガーベッジ・コレクターが複数回実行される可能性があることには注意してください。
PhantomReference クラスは、参照しているオブジェクトを回収する直前を監視したい場合にのみ役立ちます。つまり、回収直前のクリーンアップ操作の実行に使用できます。PhantomReference は、ReferenceQueue クラスとともに使用しなければなりません。ReferenceQueue は、通知のメカニズムとして機能するので必要なものです。オブジェクトがファントム可到達であるとガーベッジ・コレクターが判断すると、PhantomReference オブジェクトがそのReferenceQueue に置かれます。PhantomReference オブジェクトをReferenceQueue に置かれたということは、PhantomReference オブジェクトが参照したオブジェクトがファイナライズされており、いつでも回収できる状態にあるということを示す通知となります。これにより皆さんは、オブジェクト・メモリーが再利用される直前に措置を講ずることができます。
ガーベッジ・コレクターは、実行のたびに、もはや強可到達ではなくなったオブジェクト・メモリーを随意に解放します。ガーベッジ・コレクターが、ソフト可到達オブジェクトを発見すると、以下のようになります。
-
SoftReferenceオブジェクトの対象フィールドがnullに設定され、heapオブジェクトを参照しなくなる。 -
SoftReferenceによって参照されていたheapオブジェクトがfinalizableと宣言される。 -
heapオブジェクトのfinalize()メソッドが実行され、そのメモリーが解放されると、SoftReferenceオブジェクトが、(存在する場合は)ReferenceQueueに追加される。
ガーベッジ・コレクターが、弱可到達オブジェクトを発見すると、以下のようになります。
-
WeakReferenceオブジェクトの対象フィールドがnullに設定され、heapオブジェクトを参照しなくなる。 -
WeakReferenceによって参照されていたheapオブジェクトがfinalizableと宣言される。 -
heapオブジェクトのfinalize()メソッドが実行され、そのメモリーが解放されると、WeakReferenceオブジェクトが、(存在する場合は)ReferenceQueueに追加される。
ガーベッジ・コレクターが、ファントム可到達オブジェクトを発見すると、以下のようになります。
-
PhantomReferenceによって参照されているheapオブジェクトがfinalizableと宣言される。 - ソフト参照や弱参照と異なり、
PhantomReferenceは、ヒープ・オブジェクトが解放される前にReferenceQueueに追加されます (すべてのPhantomReferenceオブジェクトは、関連するReferenceQueueと一緒に作成しなければならないことを思い出してください)。これにより、ヒープ・オブジェクトが再利用される前に措置を講ずることができます。
リスト1のコードについて考えてみましょう。図1は、そのコードの実行を示しています。
リスト1. WeakReferenceとReferenceQueueを使用するコード例
//Create a strong reference to an object
MyObject obj = new MyObject(); //1
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue(); //2
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq); //3
|
図1. リスト1のコードの行 //1、//2、//3の実行後のオブジェクト・レイアウト
図1は、各コード行の実行後のオブジェクトの状態を示しています。行 //1でオブジェクトMyObject を作成し、行 //2でReferenceQueue オブジェクトを作成します。行 //3で、対象のMyObjectとReferenceQueueを参照するWeakReference オブジェクトを作成します。obj、rq、wr の各オブジェクト参照はすべて、強参照であることに注意してください。これらの参照クラスが働くようにするには、obj をnull に設定して、MyObject オブジェクトの強参照を解除しなければなりません。これを行わない限りMyObject オブジェクトは再利用されず、参照クラスの利点がまったくなくなることを思い出してください。
各参照クラスにはget() メソッドがあり、ReferenceQueue クラスにはpoll() メソッドがあります。get() メソッドは、対象オブジェクトへの参照を戻します。PhantomReference でget() を呼び出すと、必ずnull を戻します。これは、PhantomReference が、コレクションの追跡にのみ使用されるからです。poll() メソッドは、キューに追加された参照オブジェクトを戻し、キューに何もない場合はnull を戻します。したがって、リスト1の実行後にget() メソッドとpoll() メソッドを呼び出した結果は、以下のようになります。
wr.get(); //returns reference to MyObject rq.poll(); //returns null |
ここで、ガーベッジ・コレクターが動作するとします。MyObject オブジェクトが解放されていないので、get() メソッドとpoll() メソッドは同じ値を戻します。obj は、依然としてそれに対する強参照を維持します。実際には、オブジェクト・レイアウトは変わらず、図1のように見えます。しかし、以下のコードを考えてみましょう。
obj = null; System.gc(); //run the collector |
このコードを実行すると、オブジェクト・レイアウトは図2に示すようになります。
図2. obj = null後のオブジェクト・レイアウト (ガーベッジ・コレクター実行中)
ここでは、get() メソッドとpoll() メソッドの呼び出し結果が変わります。
wr.get(); //returns null rq.poll(); //returns a reference to the WeakReference object |
この状態が示しているのは、初めはWeakReference オブジェクトによって保持されていたMyObject オブジェクトがもはや取り出せなくなったことです。つまり、ガーベッジ・コレクターがMyObject 用のメモリーを解放し、WeakReference オブジェクトがそのReferenceQueue に配置可能になったということです。したがって、WeakReference またはSoftReference クラスのget() メソッドがnull を戻すと、オブジェクトがfinalizable と宣言され、必ずというわけではありませんが、回収された可能性があるということがわかります。ファイナライズが完了し、heap オブジェクトのメモリーが回収された場合にのみ、WeakReference またはSoftReference が、関連するReferenceQueue に置かれます。リスト2は、こうした原理の一部を示す完全に機能するプログラムを示しています。多くのコメントとprintステートメントがあるので、コードは比較的簡単に理解できます。
リスト2. 参照クラスの構造を示す完全なプログラム
import java.lang.ref.*;
class MyObject
{
protected void finalize() throws Throwable
{
System.out.println("In finalize method for this object: " + this);
}
}
class ReferenceUsage
{
public static void main(String args[])
{
hold();
release();
}
public static void hold()
{
System.out.println("Example of incorrectly holding a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it's on the ref queue yet
System.out.println("Polling the reference queue returns " + rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Calling GC");
System.gc();
System.out.println("Polling the reference queue returns " + rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
public static void release()
{
System.out.println("");
System.out.println("Example of correctly releasing a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it's on the ref queue yet
System.out.println("Polling the reference queue returns " + rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Set the obj reference to null and call GC");
obj = null;
System.gc();
System.out.println("Polling the reference queue returns " + rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
}
|
こうしたクラスの背後にある考えは、アプリケーションが存続する間メモリー内にオブジェクトを固定するのを回避することです。固定するのではなく、オブジェクトへの参照をソフトに、あるいは弱く、またはファントムに行い、ガーベッジ・コレクターが随意にオブジェクトを解放できるようにします。このような使用法は、アプリケーションがその存続期間中使用するヒープ・メモリーの量を最小限に抑えたい場合に有効となります。これらのクラスを利用する場合は、オブジェクトへの強参照を使用することはできないということを覚えておかなければなりません。強参照を使用すると、これらのクラスがもたらす利点をすべて失うことになります。
また、オブジェクトを使用する前に、正しいプログラミング・イディオムを使用して、コレクターがそれを再利用しているかどうかをチェックし、再利用している場合には、まずオブジェクトを再作成しなければなりません。このプロセスは、さまざまなプログラミング・イディオムによって行うことができます。誤ったイディオムを選択すると、問題が発生する可能性があります。リスト3の、WeakReference から対象オブジェクトを検索するためのコード・イディオムを見てみましょう。
リスト3. 対象オブジェクトを検索するためのイディオム
obj = wr.get();
if (obj == null)
{
wr = new WeakReference(recreateIt()); //1
obj = wr.get(); //2
}
//code that works with obj
|
このコードを理解した後で、リスト4の、WeakReference から対象オブジェクトを検索するための代替コード・イディオムを見てみましょう。
リスト4. 対象オブジェクトを検索するための代替イディオム
obj = wr.get();
if (obj == null)
{
obj = recreateIt(); //1
wr = new WeakReference(obj); //2
}
//code that works with obj
|
これら2つのイディオムを比較して、どちらが適切に機能し、どちらが機能しないかを判断できるかどうか考えてみましょう。リスト3に示すイディオムは必ずしも適切に機能しませんが、リスト4のイディオムは機能します。リスト3のイディオムが不完全である理由は、if ブロックの本文が完成した後に、obj が nullでないことが保証されていないということです。リスト3で、行 //1の後、行 //2が実行される前に、ガーベッジ・コレクターが実行されるとどうなるかについて考えてみましょう。recreateIt() メソッドがオブジェクトを再作成しますが、強参照ではなくWeakReference によって参照されます。つまり、再作成されたオブジェクトに行 //2が強参照を割り当てる前にコレクターが実行されると、オブジェクトが失われ、wr.get() がnull を戻します。
リスト4では、行 //1がオブジェクトを再作成し、それに強参照を割り当てるので、そのような問題は起こりません。したがって、ガーベッジ・コレクターがこの行の後で、しかも行 //2の前に実行されても、オブジェクトは再利用されないということになります。続いて、obj に対するWeakReference が行 //2で作成されます。このif ブロックの後にobj を処理してから、obj をnull に設定すると、ガーベッジ・コレクターがこのオブジェクトを再利用できるようになり、弱参照機能を完全に利用できます。リスト5は、今まで説明したイディオムの違いを示す完結したプログラムです (このプログラムを実行するには、プログラムが実行されるディレクトリーに「temp.fil」ファイルが必要です)。
リスト5. 正しいプログラミング・イディオムと誤ったプログラミング・イディオムを示す完結したプログラム
import java.io.*;
import java.lang.ref.*;
class ReferenceIdiom
{
public static void main(String args[]) throws FileNotFoundException
{
broken();
correct();
}
public static FileReader recreateIt() throws FileNotFoundException
{
return new FileReader("temp.fil");
}
public static void broken() throws FileNotFoundException
{
System.out.println("Executing method broken");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj.
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it in a WeakReference");
wr = new WeakReference(recreateIt());
System.gc(); //FileReader object is NOT pinned...there is no
//strong reference to it. Therefore, the next //line can return null.
obj = (FileReader)wr.get();
}
System.out.println("wr refers to object " + wr.get());
}
public static void correct() throws FileNotFoundException
{
System.out.println("");
System.out.println("Executing method correct");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it in a WeakReference");
obj = recreateIt();
System.gc(); //FileReader is pinned, this will not affect //anything.
wr = new WeakReference(obj);
}
System.out.println("wr refers to object " + wr.get());
}
}
|
参照クラスは、適切な状態で使用した場合に有効なものとなります。しかし、その有効性は、ガーベッジ・コレクターの予測できない振る舞いに依存しているという事実によって弱められます。また、それらの効果的な使用は正しいプログラミング・イディオムの適用にも依存しているため、そうしたクラスがどのように実装されているのか、またそれらに対してどのようにプログラミングするのかを理解することが不可欠です。
- Sam Borman氏が、IBM Garbage Collectorについて興味深い連載記事を執筆しています。第1回 ではオブジェクトの割り振りについて、第2回ではガーベッジ・コレクションの詳細について
説明しています。
- Jeff Friesen氏は、JavaWorld の記事 (2002年1月) で、Reference Object APIを使用して、イメージ・キャッシュを管理する方法、有効オブジェクト (significant object) が強可到達でなくなったときに通知を取得する方法、およびファイナライズ後のクリーンアップを実行する方法を示しています。
- developerWorksJava technologyゾーンでJavaプログラミングに関する数多くの参考文献をご覧ください。
Peter Haggarは、アメリカのノースカロライナ州にあるResearch Triangle Parkに勤務するIBMシニア・ソフトウェア・エンジニアであり、 「 Practical Java Programming Language Guide」 (Addison-Wesley 社発行) の著者です。彼はまた、Javaプログラミングに関する多数の記事を発表しています。彼は、開発ツール、クラス・ライブラリー、およびオペレーティング・システムなど、幅広いプログラミング経験を持っています。IBMでは、未来のインターネット・テクノロジーに携わり、現在は高性能なWebサービスを中心に研究を行っています。Peterは、多くの業界の会議で頻繁に技術的なスピーチを行っています。彼はIBMに14年以上にわたって勤務しており、また、Clarkson Universityよりコンピューター・サイエンスのB.S. を取得しています。Peterの連絡先はhaggar@us.ibm.com です。