JavaRush /Java Blog /Random EN /phantom reference in java

phantom reference in java

Published in the Random EN group
Hello! In today's lesson, we will talk in detail about "phantom references" (PhantomReference) 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 references in Java:
  1. StrongReference (regular references that we create when creating an object):

    Cat cat = new Cat()

    cat in this example is a strong link.

  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 kinds are typed (for example, SoftReference<Integer> , WeakReference<MyClass> ). Classes SoftReference, WeakReferenceand PhantomReferenceare inherited from class Reference. The most important methods when working with these classes are:
  • get()- returns the object referred to by this link;

  • clear()— removes the link to the object.

You remember these methods from lectures on SoftReferenceand WeakReference. It is important to remember that they work differently with different kinds of links. We will not consider the first three types in detail today, 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 at all. 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". In the first pass, it only looks at the objects, and, if necessary, marks it as "unnecessary, to be deleted." If this object had a method overriddenfinalize(), it is called. Or not called - how lucky. You probably remember that finalize()- 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. We don't know if the method will be called finalize(). Plus, during operation, finalize()a strong link to an object can be created, and then it will not be deleted at all. On memory-demanding systems, this can easily lead to OutOfMemoryError. All this encourages us to use phantom links . The point is that it changes the behavior of the garbage collector. If only phantom references remain to the object, then it has:
  • the method is called finalize()(if it is overridden);

  • if finalize()nothing has changed after work and the object can still be deleted, the 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 removed only after the phantom reference has been called 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 specifically "load" objects with data when they are created (add 50 million "x" characters to each object) in order to take up more memory. Also, we override the method on purpose finalize()to see if it worked. Next, we need a class that will inherit from PhantomReference. Why do we need such a class? Everything is simple. This way we can add additional logic to the method clear()to see if the phantom reference cleanup actually happened (which means 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 need a separate thread that will wait for the garbage collector to do its job, and ReferenceQueuephantom references will appear in our queue. As soon as such a link enters 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(): we will take it out into 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 queue monitoring thread has started! Garbage collection is called! The finalize method has been called on the TestClass object!!! ref = MyPhantomReference@4554617c Garbage collection is called! Phantom Link Cleanup! Deleting 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 madecleanup()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 related to phantom links. But it will be good if you remember at least the main points. First , these are the weakest links of all. They only come into play when there are no other references left to the object. The list of links that we have given above goes in descending order of strength: StrongReference-> SoftReference-> WeakReference-> PhantomReference The phantom link will only come into action when there are neither Strong, nor Soft, nor Weak links on our object :) Secondly , the method get()for phantom reference always returnsnull. Here is a simple example where we create three different types of links for three different kinds 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 soft reference and weak reference, but returned nullfor phantom. Thirdly , the main area of ​​​​use of phantom references is complex procedures for removing objects from memory. That's all! :) This concludes our today's lesson. 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