Close

Java Garbage Collection - Understanding Phantom Reference with examples

[Updated: Apr 27, 2018, Created: Apr 23, 2018]

PhantomReference is another subclass of Reference (check out last tutorial) which is a way to know when object is about to be garbage collected.

Object#finalize() can also be used to know when object is to be garbage collected, but PhantomReference is more reliable, efficient and flexible mechanism.

According to API docs:

An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.

The class PhantomReference overrides get() method which always returns null, instead of super's class 'referent' instance. That is done to ensure that the referent of a phantom reference may not be accessible. (To avoid the same disadvantage of using finalize() which can resurrect the target object).

Let's understand how PhantomReference can be used with example.

Example

The PhantomReference<T> constructor

Following is the only constructor available:

PhantomReference(T referent, ReferenceQueue<? super T> q)

We must create an instance of java.lang.ref.ReferenceQueue to use PhantomReference.

When an object becomes unreachable, Garbage Collector will clear it from the PhantomReference's referent and the PhantomReference itself is added to the associated queue. Our program may remove references from the queue by polling.

Let's first define an object which will become unreachable and will be gc'ed during intensive memory operations

public class MyObject {
  private int[] ints = new int[1000];
  private final String name;

  public MyObject(String name) {
      this.name = name;
  }

  @Override
  public String toString() {
      return name;
  }

  @Override
  protected void finalize() throws Throwable {
      System.out.printf("%s is finalizing.%n", name);
  }
}

Our object is also overriding finalize() method, to compare the two mechanisms of knowing when object is to be garbage collected.

Using PhantomReference

public class PhantomRefExample {

  public static void main(String[] args) {
      ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();

      MyObject myObject1 = new MyObject("phantom");
      Reference<MyObject> ref = new PhantomReference<>(myObject1, referenceQueue);
      System.out.println("ref#get(): " + ref.get());
      MyObject myObject2 = new MyObject("normal");

      //make objects unreacheable
      myObject1 = null;
      myObject2 = null;

     if(checkObjectGced(ref, referenceQueue)){
         takeAction();
     }

      System.out.println("-- do some memory intensive work --");
      for (int i = 0; i < 10; i++) {
          int[] ints = new int[100000];
          try {
              Thread.sleep(10);
          } catch (InterruptedException e) {
          }
      }
      if(checkObjectGced(ref, referenceQueue)){
         takeAction();
      }
  }

  private static boolean checkObjectGced(Reference<MyObject> ref, ReferenceQueue<MyObject> referenceQueue) {
      boolean gced = false;
      System.out.println("-- Checking whether object garbage collection due --");
      Reference<? extends MyObject> polledRef = referenceQueue.poll();
      System.out.println("polledRef: "+polledRef);
      System.out.println("Is polledRef same: "+ (gced=polledRef==ref));
      if(polledRef!=null) {
          System.out.println("Ref#get(): " + polledRef.get());
      }
      return gced;
  }

  private static void takeAction() {
      System.out.println("pre-mortem cleanup actions");
  }
}
ref#get(): null
-- Checking whether object garbage collection due --
polledRef: null
Is polledRef same: false
-- do some memory intensive work --
normal is finalizing.
phantom is finalizing.
-- Checking whether object garbage collection due --
polledRef: java.lang.ref.PhantomReference@29453f44
Is polledRef same: true
Ref#get(): null
pre-mortem cleanup actions

We ran above code with JVM option -Xmx1m -Xms1m.

As seen above ReferenceQueue#poll() will return null (does not block) when object is not yet enqueued for garbage collection. When it is eligible for garbage collection this method will return the related PhantomReference instance. At that point, our code can take a desirable action (e.g. cleaning up).

Also there seems to be no difference between a normal object and phantom referenced object with regards to when they are gc'ed. They both are gc'ed about the same time.

In following example, we will see at what time ReferenceQueue.poll() returns the target phantom reference, relative to the time when Object#finalize() method been called.

public class PhantomRefExample2 {
  private static boolean finishFlag;

  public static void main(String[] args) {
      ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();

      MyObject myObject1 = new MyObject("phantom");
      Reference<MyObject> ref = new PhantomReference<>(myObject1, referenceQueue);
      startMonitoring(referenceQueue, ref);
      System.out.println("ref#get(): " + ref.get());
      MyObject myObject2 = new MyObject("normal ");

      //make objects unreacheable
      myObject1 = null;
      myObject2 = null;

      System.out.println("-- do some memory intensive work --");
      for (int i = 0; i < 10; i++) {
          int[] ints = new int[100000];
          try {
              Thread.sleep(10);
          } catch (InterruptedException e) {
          }
      }
      System.out.println("-- heavy work finished --");
      finishFlag = true;
  }

  private static void startMonitoring(ReferenceQueue<MyObject> referenceQueue, Reference<MyObject> ref) {
      ExecutorService ex = Executors.newSingleThreadExecutor();
      ex.execute(() -> {
          while (referenceQueue.poll()!=ref) {
              //don't hang forever
              if(finishFlag){
                  break;
              }
          }
          System.out.println("-- ref gc'ed --");

      });
      ex.shutdown();
  }
}
ref#get(): null
-- do some memory intensive work --
normal is finalizing.
phantom is finalizing.
-- ref gc'ed --
-- heavy work finished --

As seen ReferenceQueue#poll() returns the same ref instance just after finalize() method is called. In fact only those References are enqueued which have been finalized already.

Note that, in Java 9, Phantom references are automatically cleared internally. Previously, the referent was kept alive (even though get() method always returns null) until PhantomReference objects were GC'ed. (more details).

Also Object#finalize() method has been deprecated in Java 9.

Example Project

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.3.9

Phantom Reference Example Select All Download
  • phantom-reference-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • PhantomRefExample.java

    See Also