JavaRush /Java Blog /Random EN /PhantomReference in Java

PhantomReference in Java

Published in the Random EN group
Hello! In today's lesson, we will talk in detail about Phantom References in Java. What kind of links are these, why are they called “phantom” and how to use them? As you remember, there are 4 types of links in Java:
  1. StrongReference (regular references that we create when creating an object):

    Cat cat = new Cat()

    cat in this example is a Strong reference.

  2. SoftReference (soft reference). We had a lecture about these links.

  3. WeakReference (weak reference). There was a lecture about them too, here .

  4. PhantomReference (phantom reference).

Objects of the last three types are typed (for example, SoftReference<Integer> , WeakReference<MyClass> ). Classes SoftReference, WeakReferenceand PhantomReferenceinherit from class Reference. The most important methods when working with these classes are:
  • get()- returns the object this link refers to;

  • clear()— deletes a reference to an object.

You remember these methods from lectures about SoftReferenceand WeakReference. It's important to remember that they work differently with different types of links. Today we will not consider the first three types in detail, but will talk about phantom links. We will also touch on other types of links, but only in the part where we will talk about how phantom links differ from them. Go! :) Let's start with why we need phantom links in the first place. As you know, the garbage collector (Garbage Collector or gc) is responsible for freeing memory from unnecessary Java objects . The collector removes the object in two "passes". On the first pass, it only looks at objects, and, if necessary, marks them as “unnecessary and to be deleted.” If this object had a method overridden finalize(), it is called. Or it’s not called - depending on your luck. You probably remember that this finalize()is a fickle thing :) In the second pass of the collector, the object is deleted and the memory is freed. This unpredictable behavior of the garbage collector creates a number of problems for us. We don't know exactly when the garbage collector will start working. We don't know whether the method will be called finalize(). Plus, during operation finalize()a strong link to the object can be created, and then it will not be deleted at all. On memory-hungry systems, this can easily lead to OutOfMemoryError. All this pushes us towards using phantom links . The point is that this changes the behavior of the garbage collector. If there are only phantom links left to the object, then it has:
  • the method is called finalize()(if it is overridden);

  • if after work finalize()nothing has changed and the object can still be deleted, a phantom reference to the object is placed in a special queue - ReferenceQueue.

The most important thing to understand when working with phantom references is that an object is not removed from memory as long as its phantom reference is in this queue . It will be deleted only after the phantom link's clear(). Let's look at an example. First, let's create a test class that will store some data.
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("У an object TestClass вызван метод finalize!!!");
   }
}
We deliberately “load” objects with data when creating them (adding 50 million “x” characters to each object) in order to take up more memory. In addition, we specifically override the method finalize()to see that it worked. Next we need a class that will inherit from PhantomReference. Why do we need such a class? It's simple. This way we can add additional logic to the method clear()to see that the phantom reference has actually been cleared (and therefore the object has been deleted).
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("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Next, we will need a separate thread that will wait for the garbage collector to do its thing, and ReferenceQueuephantom links will appear in our queue. As soon as such a link gets into the queue, its method will be called 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("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
And finally, we need a method main(): let's put it in a separate class Main. In it we will create an object TestClass, a phantom reference to it and a queue for phantom references. After that we will call the garbage collector and see what happens :)
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("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Console output: ref = MyPhantomReference@4554617c The thread monitoring the queue has started! Garbage collection is called! The finalize method has been called on the TestClass object!!! ref = MyPhantomReference@4554617c Garbage collection is being called! Cleaning up the phantom link! Removing an object from memory! What do we see here? Everything happened as we planned! Our object class had a method overridden finalize()and it was called while the collector was running. Next, the phantom link was queued ReferenceQueue. There she had a method called clear()(from which we did cleanup()to add output to the console). As a result, the object was removed from memory. Now you see exactly how it works :) Of course, you don't need to memorize all the theory associated with phantom links. But it will be good if you remember at least the main points. Firstly , these are the weakest links of all. They come into play only when there are no other references to the object. The list of links that we have given above is in descending order of strength: StrongReference-> SoftReference-> WeakReference-> PhantomReference The phantom link will enter into battle only when there are no Strong, Soft, or Weak links to our object :) Secondly , the method get()for phantom link always returns null. Here's a simple example where we create three different types of links for three different types of cars:
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());

   }
}
Console output: Sedan@4554617c HybridAuto@74a14482 null The method get()returned quite normal objects for the soft link and weak link, but returned nullfor the phantom one. Thirdly , the main area of ​​use of phantom references is complex procedures for removing objects from memory. That's all! :) This concludes our lesson for today. But theory alone won’t get you far, so it’s time to get back to solving problems! :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION