やあ!今日の説明では、Java の「ファントム参照」(PhantomReference) について詳しく説明します。これらはどのような参考文献ですか? なぜ「ファントムリファレンス」と呼ばれるのでしょうか? どのように使用されますか? 思い出していただけると思いますが、Java には 4 種類の参照があります。
-
StrongReference (オブジェクトの作成時に作成する通常の参照):
Cat cat = new Cat()
この例では、cat が強参照です。
-
SoftReference (ソフトリファレンス)。そんな参考文献についての授業がありました。
-
WeakReference (弱い参照)。ここでもそれらについての教訓がありました。
-
PhantomReference (ファントムリファレンス)。
-
get() — 参照されたオブジェクトを返します。
- clear() — オブジェクトへの参照を削除します。
finalize()
メソッドがオブジェクトに対してオーバーライドされた場合、そのメソッドが呼び出されます。あるいは、それは呼ばれていないのかもしれません。すべては運が良いかどうかにのみ依存します。おそらくそれがfinalize()
気まぐれであることを覚えているでしょう :) ガベージ コレクターの 2 回目のパスでは、オブジェクトが削除され、メモリが解放されます。ガベージ コレクターの予測できない動作は、私たちに多くの問題を引き起こします。ガベージ コレクターがいつ実行を開始するのか正確にはわかりません。finalize()
メソッドが呼び出されるかどうかはわかりません。さらに、オブジェクトへの強い参照は、そのメソッドの実行中に作成できますfinalize()
。この場合、オブジェクトはまったく削除されません。利用可能なメモリを大量に要求するプログラムの場合、これは簡単にOutOfMemoryError
. これらすべてにより、ファントム参照を使用することになります。。実際、これによりガベージ コレクターの動作が変わります。オブジェクトにファントム参照のみがある場合は、次のようになります。
-
そのFinalize()メソッドが呼び出されます (オーバーライドされた場合)
-
Finalize()メソッドが終了しても何も変化がなく、オブジェクトをまだ削除できる場合は、オブジェクトへのファントム参照が特別なキューReferenceQueueに配置されます。
public class TestClass {
private StringBuffer data;
public TestClass() {
this.data = new StringBuffer();
for (long i = 0; i < 50000000; i++) {
this.data.append('x');
}
}
@Override
protected void finalize() {
System.out.println("The finalize method has been called on the TestClass object");
}
}
オブジェクトを作成するときは、より多くのメモリを消費するために、意図的に大きな「負荷」を与えます (各オブジェクトに 5,000 万個の「x」文字を追加する)。さらに、finalize()メソッドをオーバーライドして、それが実行されることを確認します。次に、 PhantomReferenceを継承するクラスが必要です。なぜそのようなクラスが必要なのでしょうか? それはすべて簡単です。これにより、ファントム参照が実際にクリアされた (オブジェクトが削除されたことを意味する) ことを検証するために、追加の ロジックを clear() メソッドに追加できるようになります。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {
public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {
super(obj, queue);
Thread thread = new QueueReadingThread<TestClass>(queue);
thread.start();
}
public void cleanup() {
System.out.println("Cleaning up a phantom reference! Removing an object from memory!");
clear();
}
}
次に、ガベージ コレクターがそのジョブを実行するのを待機する別のスレッドが必要です。これにより、ファントム リンクが ReferenceQueue に表示されます。このような参照がキューに入るとすぐに、cleanup()メソッドが呼び出されます。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class QueueReadingThread<TestClass> extends Thread {
private ReferenceQueue<TestClass> referenceQueue;
public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
this.referenceQueue = referenceQueue;
}
@Override
public void run() {
System.out.println("The thread monitoring the queue has started!");
Reference ref = null;
// Wait until the references appear in the queue
while ((ref = referenceQueue.poll()) == null) {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
throw new RuntimeException("Thread " + getName() + " was interrupted!");
}
}
// As soon as a phantom reference appears in the queue, clean it up
((MyPhantomReference) ref).cleanup();
}
}
最後にmain()メソッドが必要です。これを別のMainクラスに置きます。このメソッドでは、 TestClassオブジェクト、そのオブジェクトへのファントム参照、およびファントム参照用のキューを作成します。その後、ガベージ コレクターを呼び出して、何が起こるか見てみましょう:)
import java.lang.ref.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
Reference ref = new MyPhantomReference<>(new TestClass(), queue);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Collecting garbage!");
System.gc();
Thread.sleep(300);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Collecting garbage!");
System.gc();
}
}
コンソール出力:
ref = MyPhantomReference@4554617c
The thread monitoring the queue has started!
Collecting garbage!
The finalize method has been called on the TestClass object
ref = MyPhantomReference@4554617c
Collecting garbage!
Cleaning up a phantom reference!
Removing an object from memory!
ここで何が見えるでしょうか?すべては私たちの計画通りに起こりました!オブジェクトのFinalize()メソッドはオーバーライドされ、ガベージ コレクターの実行中に呼び出されました。次に、ファントム参照が ReferenceQueue に追加されました。その間に、clear()メソッドが呼び出されます (その中で、コンソールに出力するためにcleanup()を呼び出しました)。最後に、オブジェクトはメモリから削除されました。これで、これがどのように機能するかがわかりました :) もちろん、ファントム参照に関する理論をすべて暗記する必要はありません。しかし、少なくとも要点だけは覚えておいた方が良いでしょう。まず、これらはすべての参照の中で最も弱い参照です。これらは、オブジェクトへの他の参照が残っていない場合にのみ有効になります。 上で示した参照のリストは、最も強いものから最も弱いものへの降順で並べ替えられています。 StrongReference -> SoftReference -> WeakReference -> PhantomReference ファントム参照は、オブジェクトへの強い参照、ソフト参照、または弱い参照がない場合にのみ戦闘に参加します。 ) 次に、get()メソッドはファントム参照に対して常にnullを返します。以下は、3 つの異なるタイプの車に対して 3 つの異なるタイプの参照を作成する簡単な例です。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
HybridAuto hybrid = new HybridAuto();
F1Car f1car = new F1Car();
SoftReference<Sedan> softReference = new SoftReference<>(sedan);
System.out.println(softReference.get());
WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
System.out.println(weakReference.get());
ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();
PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
System.out.println(phantomReference.get());
}
}
コンソール出力:
Sedan@4554617c
HybridAuto@74a14482
null
get ()メソッドは、ソフト参照と弱参照に対しては完全に通常のオブジェクトを返しましたが、ファントム参照に対してはnullを返しました。3 番目に、ファントム参照は主にメモリからオブジェクトを削除する複雑な手順で使用されます。それでおしまい!:) 今日のレッスンはこれで終わりです。ただし、理論だけで先に進むことはできないため、タスクの解決に戻りましょう。:)
GO TO FULL VERSION