{{announcement.body}}
{{announcement.title}}

Understanding Types of References in Java

DZone 's Guide to

Understanding Types of References in Java

Learn more about the different types of references in Java.

· Java Zone ·
Free Resource

two coffees with plants

Learn more about the different types of references in Java.

There are four types of references in Java. And for each of them, the garbage collector behaves a little bit differently.

Below, I will try to explain each type of reference's characteristics and possible use cases, specifically strongweaksoft, and phantom references.

You may also like:  Java Garbage Collector and Reference Types

Strong References

The default type of reference in Java is a strong reference. When you define an object regularly, it will have a strong reference, and as long as an object has a strong reference, the object will not be eligible for the garbage collector. When we set the strong variable to null, that object will become eligible for garbage collection. As long as the GC runs the space that the object uses, it will be reclaimed.     

Let's see a strong reference behavior with an example.

Below, the StrongReference class has a nested class named A. It creates an instance of A and sets it to null. At that time, that object should be eligible for GC.

package references;

import java.io.File;
import java.util.Objects;

public class StrongReference {
    private static final int RETRY = 3;
    public static void main(String[] args) throws InterruptedException {
        deleteOldDumps();
        A a = new A("Strong Reference");
        references.HeapDump.dumpHeap("java/heap-dumps/strongRefBeforeGCEligible.hprof", false);
        a = null; // Make the strong reference eligible for GC
        references.HeapDump.dumpHeap("java/heap-dumps/strongRefBeforeGC.hprof", false);
        runGC();
        references.HeapDump.dumpHeap("java/heap-dumps/strongRefAfterGC.hprof", false);
    }

    static class A {
        A(String s) {
            this.s = s;
        }
        String s;
    }

    static void deleteOldDumps() throws InterruptedException {
        boolean isDeleted = deleteFiles();
        int attempt = 0;
        while (!isDeleted && attempt++ < RETRY) {
            isDeleted = deleteFiles();
            Thread.sleep(1000 * attempt);
        }
    }

    private static boolean deleteFiles() {
        File dir = new File("java/heap-dumps");
        File[] files = dir.listFiles();
        for (File file : Objects.requireNonNull(files)) {
            file.delete();
        }
        return files.length != 0;
    }

    static void runGC() throws InterruptedException {
        System.out.println("Running GC..");
        System.gc(); // Hint to run gc
        Thread.sleep(2000L); // sleep hoping to let GC thread run
        System.out.println("Finished running GC..");
    }
} 


Here, we use "com.sun.management:type=HotSpotDiagnostic" mbean to dump memory to later inspect it with that command-line utility. Remember: You must delete the previous dump files with the deleteOldDumps method; otherwise, the heap dump will give you the "File exists" error.

We use this class to use the HotSpotDiagnostic mbean and dump memory to a hprof file.

Please keep in mind that the second parameter of the dumpHeap method is a boolean, and if we set it true, it will only dump live objects. This will force us to run the GC before taking the heap dump so that we do not need to hint with System.gc(). In this example, we set the second parameter to false and try to hint GC to run with System.gc() instead.

Note that in order to monitor it, GC runs with the System.gc() call. We can run the class with the "-verbose:gc" JVM parameter so that we can see the GC running in logs.

We take three dumps files before setting the object to null, after setting to it null and hinting to run GC.

Now, let's run the class and follow the steps below using our jhat command to see our memory snapshot through the web interface:

  • Run "jhat -J-Xmx1g -port 8000 strongRefBeforeGCEligible.hprof"  with different ports for all three hprof files.

  • Open localhost:8000/histo (We will use the heap histogram to examine the references)

The output of the program is:

Running GC..
[GC (System.gc())  21053K->1256K(502784K), 0.0010238 secs]
[Full GC (System.gc())  1256K->979K(502784K), 0.0061717 secs]
Finished running GC..


For the web interface of  jhat, we will see the following three results for three different hprof files.


Class

class references.StrongReference$A



Instance Count

1



Total Size

8


strongRefBeforeGCEligible.hprof(localhost:8000/histo/)


Class

class references.StrongReference$A



Instance Count

1



Total Size

8


strongRefBeforeGC.hprof(localhost:8001/histo/) 


Class

class references.StrongReference$A



Instance Count

0



Total Size

0


strongRefAfterGC.hprof(localhost:8002/histo/)

As we expected, after GC runs, the non-referenced object of class A is reclaimed by GC.

Weak References

A weak reference can be created with the java.lang.ref.WeakReference class. If the JVM sees an object that has only a weak reference during GC, it will be collected.

There is a get() method of WeakReference that will return an object itself if it is not garbage-collected yet or returns null if it is removed already.

Weak references can be used to prevent memory leaks. For example, for a temporary object, if you want to keep some additional information, you generally use a global map with the temporary object as a key and additional information as a value. This can be seen as an in-memory cache.

With a strong referenced map, even if the temporary object lifecycle ends, since we reference from the map, the temporary object and the additional info will remain in heap for more time than the expected lifecycle of the object. This causes a memory leak, but it can be prevented using weak references.

Fortunately, we do not need to implement this as Java has a built-in WeakHashMap class. If you use this map, GC will collect the temporary object if the only reference to this object is from WeakHashMap.

Note that WeakHashMap can hold a ReferenceQueue, which is defined during the creation of the weak reference. This queue will hold the reference object (not the referent) after GC runs. We can poll the reference after the referent garbage is collected. Before GC collects, the referent queue poll will return null.

Below is an example of using WeakHashMap and WeakReference.

package references;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import static references.StrongReference.runGC;
import static references.StrongReference.deleteOldDumps;

public class WeakReferenceTest {

    public static void main(String[] args) throws InterruptedException {
        deleteOldDumps();

        //1.) WeakHashMap example
        System.out.println("1.) WeakHashMap example:");
        A a = new A("a");
        List<Object> objects = new ArrayList<>();
        B b = new B(objects);
        Map<A, B> map = new WeakHashMap<>();
        map.put(a, b);
        a = null;
        b = null;
        //Note that B will be GC'ed after the second GC because expungeStaleEntries method
        //call needed to remove entry first as it holds a ref to b
        System.out.println("Map size before gc: " + map.size());
        runGC();
        //expungeStaleEntries method cleaned up the entry with null key. 
        //We expect map size as 0 
        System.out.println("Map size after gc: " + map.size());
        System.out.println("--------------------------------------------------------");
        System.out.println("2.) WeakReference example:");
        //2.) WeakReference example
        //Do not use String a referent of weak ref.
        //Since it is pooled you cannot observe weakreference.
        //WeakReference's referent property is already null after GC
        //as opposed to PhantomReference(prior to java 9)
        A a2 = new A("a");
        ReferenceQueue<A> referenceQueue = new ReferenceQueue<>();
        Reference<A> weakReference = new WeakReference<>(a2, referenceQueue); 
        HeapDump.dumpHeap("java/heap-dumps/weakRefBeforeGCEligible.hprof", false);
        a2 = null;
        System.out.println("Reference.get() before gc: " + weakReference.get());
        System.out.println("Referencequeue.poll() before gc: " + referenceQueue.poll());
        System.out.println("is enqueued: " + weakReference.isEnqueued());
        HeapDump.dumpHeap("java/heap-dumps/weakRefBeforeGC.hprof", false);
        runGC();
        HeapDump.dumpHeap("java/heap-dumps/weakRefAfterGC.hprof", false);
        System.out.println("Reference.get() after gc: " + weakReference.get());
        System.out.println("is enqueued: " + weakReference.isEnqueued());
        Reference polledRef = referenceQueue.poll(); 
        System.out.println("Refs are equal: " + (weakReference == polledRef));
        System.out.println("ReferenceQueue.poll() after gc: " + polledRef); 
       //referent is already cleared and we expect null here
        System.out.println("Referent inside reference after gc: " + polledRef.get());
        System.out.println("--------------------------------------------------------");

        System.out.println("3.) Getting inner information from weak reference" +
                " after the referent garbage collected");
        //3.) Reaching some inner information through weakreference after
        //referent the object garbage collected
        ReferenceQueue<A> referenceQueue2 = new ReferenceQueue<>();
        A a3 = new A("a3");
        String innerInfo = "Inner info";
        WeakA weakA = new WeakA(a3, innerInfo, referenceQueue2);
        a3 = null; 
        runGC();
        //should not be null but the referent inside it automatically
        //removed and polledRef.get() returns null as it is garbage collected 
        polledRef = referenceQueue2.poll();
        System.out.println("Referent inside reference after gc: " + polledRef.get());
        weakA = (WeakA) polledRef;
        System.out.println("Get inner information from the polled reference: "
                 + weakA.innerInfo);
    }

    static class A {
        public A(String s) {
            this.s = s;
        }
        String s;
    }

    static class WeakA extends WeakReference<A> {
        String innerInfo;
        public WeakA(A a, String innerInfo, ReferenceQueue<A> queue) {
            super(a, queue); 
            this.innerInfo = innerInfo;
        }
    }

    static class B {
        List<Object> largeObject;

        public B(List<Object> largeObject) {
            this.largeObject = largeObject;
        }
    }


The output of the program is:

1.) WeakHashMap example:
Map size before gc: 1
Running GC..
[GC (System.gc())  10526K->584K(502784K), 0.0008749 secs]
[Full GC (System.gc())  584K->375K(502784K), 0.0034859 secs]
Finished running GC..
Map size after gc: 0
--------------------------------------------------------
2.) WeakReference example:
Reference.get() before gc: references.WeakReferenceTest$A@6e5e91e4
Referencequeue.poll() before gc: null
is enqueued: false
Running GC..
[GC (System.gc())  23202K->1551K(502784K), 0.0012596 secs]
[Full GC (System.gc())  1551K->1284K(502784K), 0.0066106 secs]
Finished running GC..
Reference.get() after gc: null
is enqueued: true
Refs are equal: true
ReferenceQueue.poll() after gc: java.lang.ref.WeakReference@30946e09
Referent inside reference after gc: null
--------------------------------------------------------
3.) Getting inner information from weak reference after the referent garbage collected
Running GC..
[GC (System.gc())  9179K->1420K(502784K), 0.0007208 secs]
[Full GC (System.gc())  1420K->1284K(502784K), 0.0074525 secs]
Finished running GC..
Referent inside reference after gc: null
Get inner information from the polled reference: Inner info


The first part shows an example usage of WeakHashMap. We see that the map size is one before GC, but it is zero after GC, as the key is to set to null before GC. It is collected because the only reference to it is the Weak reference from the WeakHashMap.

Note that normally collecting the key will not remove the Entry from the map, but we see that the size of the map is zero. This is because there is a expungeStaleEntries method in our WeakHashMap implementation, which is called during ordinary map operations like size/put.

This method iterates through ReferenceQueue of WeakReference, and if it finds a reference, get the hash of the reference from it and use that to find and remove the Entry object from the map itself.

In turn, this lets the value of the Entry be garbage-collected in the next GC cycle. In the above example, it is a B object. It is more effective than running through all maps and removing an entry if the key is null. It uses a technique similar to the third part of the above example. By holding a reference object, which extends WeakReference, we can add some additional information to the reference itself.

Even though the inner referent inside the reference objects is cleared (after referent garbage-collected), we can still reach that inner additional information to use it later, like using the hash to remove the Entry in the map.

Lastly, the second part of the above example shows a basic WeakReference example where the object is garbage-collected only after a weak reference is made, as we can see from the jhat results below.

And again, after the web interface of  jhat, we will see the following three results for three different hprof files.


Class

class references.WeakReference$A



Instance Count

1



Total Size

8


weakRefBeforeGCEligible.hprof(localhost:8000/histo/)


Class

class references.WeakReference$A



Instance Count

1



Total Size

8


weakRefBeforeGC.hprof(localhost:8001/histo/) 


Class

class references.WeakReference$A



Instance Count

0



Total Size

0


weakRefAfterGC.hprof(localhost:8002/histo/)

Soft References

A soft reference can be created with the java.lang.ref.SoftReference class. A soft reference is garbage-collected only if there is not enough memory left in the heap. That means it can be used for memory-sensitive caches where we do not want it to be removed from memory because reading from the source is more expensive. 

Note that if an application uses almost all allowed heap memory, we may not get many advantages from using a cache. For starters, an almost full memory will make our application slow in processing and response times, as it will require a lot of swap between memory and disk. However, soft references can still be used instead of strong references in some circumstances where we want to prevent out of memory errors.

package references;

import java.lang.ref.Reference;

import java.lang.ref.ReferenceQueue;

import java.lang.ref.SoftReference;

import static references.StrongReference.deleteOldDumps;

import static references.StrongReference.runGC;

public class SoftReferenceTest {

    public static void main(String[] args) throws InterruptedException {

        deleteOldDumps();

        A a = new A("a");

      ReferenceQueue<A> referenceQueue = new ReferenceQueue<>();

        Reference<A> softReference = new SoftReference<>(a, referenceQueue);

        HeapDump.dumpHeap("java/heap-dumps/softRefBeforeGCEligible.hprof", false);

        a = null;

        System.out.println("Reference.get() before gc: " + softReference.get());

        System.out.println("Referencequeue.poll() before gc: " + referenceQueue.poll());

        System.out.println("is enqueued: " + softReference.isEnqueued());

        HeapDump.dumpHeap("java/heap-dumps/softRefBeforeGC.hprof", false);

        runGC();

        HeapDump.dumpHeap("java/heap-dumps/softRefAfterGC.hprof", false);

        System.out.println("Reference.get() after gc: " + softReference.get());

        System.out.println("is enqueued: " + softReference.isEnqueued());

        Reference polledRef = referenceQueue.poll();

        System.out.println("ReferenceQueue.poll() after gc: " + polledRef);

    }


    static class A {

        String s; 


        public A(String s) {

            this.s = s; 

       }

   }

}


The output of the program is:

Reference.get() before gc: references.SoftReferenceTest$A@5387f9e0
Referencequeue.poll() before gc: null
is enqueued: false
Running GC..
[GC (System.gc())  26317K->1544K(502784K), 0.0016028 secs]
[Full GC (System.gc())  1544K->1300K(502784K), 0.0070209 secs]
Finished running GC..
Reference.get() after gc: references.SoftReferenceTest$A@5387f9e0
is enqueued: false
ReferenceQueue.poll() after gc: null


If we check the jhat result after GC, we will see soft reference is not cleared.


Class

class references.SoftReference$A



Instance Count

1



Total Size

8


softRefBeforeGCEligible.hprof(localhost:8002/histo/)

Phantom References

A phantom reference can be created with java.lang.ref.PhantomReference class. Unlike weak and soft references whereby we can control how objects garbage-collected, a phantom reference is used for pre-mortem clean-up actions before the GC removes the object. 

It is mainly used as a replacement for the finalize method, which is unreliable and can slow down the application as the JVM uses a separate thread pool for finalization and an object with a finalize method consumes more resources than a normal object. 

Another problem with the finalize method is that it allows the objects to be resurrected during finalization, and for this reason, at least two GC cycles need to be run as the first GC makes the object finalizable and the second GC reclaims the object if it is not resurrected. And between these two GC cycles, the finalizer thread must have been run to actually reclaim the object.

If the finalizer thread does not run, more than two numbers of GC can run, and this means the object will wait to be reclaimed for a long time although it is not used anymore, which can cause an out-of-memory exception even though most the objects in heap are garbage.

And as per doc:

"You should also use finalization only when it is absolutely necessary. Finalization is a nondeterministic — and sometimes unpredictable — process. The less you rely on it, the smaller the impact it will have on the JVM and your application.

A phantom reference is enqueued to the reference queue by the garbage collector after it determines that the referent object is phantom reachable and that it has no reference left.

Note that the time the reference is enqueued is not certain and it can be any time after GC determines the referent is to reclaimable.

Note that to prevent resurrection, the phantom reference get method always returns null. Also, prior to Java 9, unlike weak and soft references, phantom-referenced objects (referent) are not automatically cleared and the referent will stay in-memory either until calling a clear method to clear it or the reference itself is garbage-collected. With Java 9, the referent is automatically cleared when the reference is enqueued.

Note that creating a phantom reference without a reference queue is useless as the get method returns null, and since there is no queue, it will never be enqueued.

As an example, when using the phantom reference to clean up objects, you can check the FileCleaningTracker class from Apache Commons, which uses PhantomReference to delete the file physically if the object representing the file is cleaned up.

Another use case, apart from doing a pre-mortem clean-up action, could be determining the time to load a heavy object after another one is cleaned up. Since a phantom reference queued on a reference queue tells us the referent itself happens to be garbage-collected, we can use that information to load another object.

However, especially before Java 9 where the referent still referenced from the phantom reference itself, even after you see the reference in the queue, we cannot be sure the object really claimed in terms of memory. Seeing the reference in the reference queue does not tell us for certain that the space that the object is using is really claimed. Instead, it means the space held by the referent object will happen to be reclaimed and there is no way to resurrect it, so we can do whatever cleanup operation we want.

Let's check out an example usage of the PhantomReference. Below, we will conduct a clean-up operation.

Note that we used the MyFinalizer class, which extends PhantomReference and holds some additional information to be used in clean-up after the object phantom reachable.

Also, note that we used a shutdown hook as a separate thread and forced the phantom reference monitoring thread to finish before the application exit. For that purpose, we used a CountDownLatch in the shutdown hook, which is a barrier allowing us to wait another thread to finish.

Finally, in the monitoring thread, we read the reference from the reference queue and performed the cleanup operation with the information we set during the creation of reference.

Keep in mind that we need to call the clear method of reference after we finish the clean-up so that the referent will be set to null and ready to clean up by GC. If we do not call a clean method on reference, we will see the referent is still there. See the shutdown hook thread code that uses reflection to get the referent. (Do not use reflection to get the referent when using phantom reference because it can break the promise of no resurrection of the phantom reference).

If you run the same code with JDK 9 or greater, you will see that the referent is automatically set to null and you do not need to call the reference.clear() method anymore.

package references;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static references.StrongReference.deleteOldDumps;
import static references.StrongReference.runGC;

public class PhantomRefTest {
    private volatile static boolean finishing = false;
    private static final long WAIT_TIME_FOR_REMOVING_PHANTOM_REFERENCE_MS = 5000L;


    public static void main(String[] args) throws InterruptedException {
      deleteOldDumps();
      PhantomRefTest phantomRefTest = new PhantomRefTest();
      A a = phantomRefTest.new A("a");
      ReferenceQueue<A> referenceQueue = new ReferenceQueue<>();
      String someInfoToUseInCleanUp = "some info";
      Reference<A> reference = phantomRefTest.new MyFinalizer(a, someInfoToUseInCleanUp, referenceQueue);
      HeapDump.dumpHeap("java/heap-dumps/phantomRefBeforeGCEligible.hprof", false);
      a = null;
      System.out.println("Phantom reference always return null: " + reference.get());
      System.out.println("Phantom reference queue return null before GC: " + referenceQueue.poll());
      HeapDump.dumpHeap("java/heap-dumps/phantomRefBeforeGC.hprof", false);
      runGC();
      HeapDump.dumpHeap("java/heap-dumps/phantomRefAfterGC.hprof", false);
      CountDownLatch countDownLatch = new CountDownLatch(1);
      Runtime.getRuntime().addShutdownHook(new Thread(() -> {
         System.out.println("Caught shutdown hook, finishing phantom reference monitoring!");
         finishing = true;
         try {
          countDownLatch.await();
          HeapDump.dumpHeap("java/heap-dumps/phantomRefAfterCleanUp.hprof", false);
          //if you check internal referent here (by looking with debugger), i
          //it will be null as we cleared it.
          //If we would not call finalizer.clear() in monitoring thread here we will see the object value.
          //Note that after java 9 the referent cleared internally not waiting the 
          //phantomref object to be cleared.
          System.out.println("Getting internal referent by reflection as reference.get() always null"); 
          Field referentField = Reference.class.getDeclaredField("referent");
          referentField.setAccessible(true);
          A referent = (A) referentField.get(reference);
          System.out.println("Check referent after clean up: " + (referent == null ? null : referent.s));
          } catch (InterruptedException | NoSuchFieldException | IllegalAccessException e) {
              e.printStackTrace();
         }
        System.out.println("Finished all running threads. Exiting!");
        }));

        phantomRefTest.monitorPhantomReference(referenceQueue, reference, countDownLatch);

    }

private void monitorPhantomReference(ReferenceQueue<A> referenceQueue, Reference<A> reference,

 CountDownLatch latch) {
        ExecutorService executorService = null;
        try {
            executorService = Executors.newSingleThreadExecutor();
            Runnable runnable = () -> {
                try {
                    while (!finishing) {
                        try {
                            System.out.println("Start reading phantom reference from queue!");
                            MyFinalizer finalizer = (MyFinalizer) referenceQueue.
                                    remove(WAIT_TIME_FOR_REMOVING_PHANTOM_REFERENCE_MS);
                            if (finalizer != null) {
                                System.out.println("We got the phantom referenced object in the queue");   
                                System.out.println("Reference queue return the same ref object: " +
                                        (finalizer == reference));
                                System.out.println("Phantom reference always return null: " +
                                        finalizer.get());
                                finalizer.cleanUp();

                                //if you do not call this, it will be cleared after phantomref 
                                //object itself cleared. It is cleared automatically after java9
                     finalizer.clear();

                           } catch(IllegalArgumentException e){
                                e.printStackTrace();
                                continue;
                            } catch(InterruptedException e){
                                continue;
                            }
                        }
                        System.out.println("Exiting phantom reference monitoring thread!");
                    } catch(Exception e){
                        e.printStackTrace();
                    } finally{
                        System.out.println("Counting down the latch in phantom

 reference monitoring thread!");
                        latch.countDown();
                    }
                } ;
                executorService.submit(runnable);
            } catch(Exception e){
                e.printStackTrace();
            } finally{
                if (executorService != null) {
                    executorService.shutdown();
                }
            }
        }

        private class A {
            public A(String s) {
                this.s = s;
            }
            String s;
        }
        /** The object will be phantomly reachable only when you have

 no reference from your app. */

        private class MyFinalizer extends PhantomReference<A> {
            String someInfoToUseInCleanUp;
            public MyFinalizer(A referent, String someInfo, ReferenceQueue<? super A> q) {
                super(referent, q);
                this.someInfoToUseInCleanUp = someInfo;
            }
            public void cleanUp() {
                System.out.println("Clean up resources with info " + someInfoToUseInCleanUp);
            }
        }
    }


The output of the program is;

Phantom reference always return null: null

Phantom reference queue return null before GC: null

Running GC..

Finished running GC..

Start reading phantom reference from queue!

We got the phantom referenced object in the queue

Reference queue return the same ref object: true

Phantom reference always return null: null

Clean up resources with info some info

Start reading phantom reference from queue!

Start reading phantom reference from queue!

Caught shutdown hook, finishing phantom reference monitoring!

Exiting phantom reference monitoring thread!

Counting down the latch in phantom reference monitoring thread!

Getting internal referent by reflection as reference.get() always return null

Check referent after clean up: null

Finished all running threads. Exiting!


Note that we run the program from the command-line and exit with CTRL+C when the monitoring thread is running inside the loop. And we see that the shutdown thread set finishing to true and waits for the monitoring thread to exit so that no code is interrupted but finished as it should finish.

For the web interface of jhat, we will see the following five results for the five different hprof files.


Class

class references.PhantomReference$A



Instance Count

1



Total Size

16


phantomRefBeforeGCEligible.hprof(localhost:8000/histo/)


Class

class references.PhantomReference$A



Instance Count

1



Total Size

16


phantomRefBeforeGC.hprof(localhost:8001/histo/)


Class

class references.PhantomReference$A



Instance Count

1



Total Size

16


phantomRefAfterGC.hprof(localhost:8002/histo/)


Class

class references.PhantomReference$A



Instance Count

0



Total Size

0


phantomRefAfterCleanUp.hprof(localhost:8003/histo/)

Class

class references.PhantomReference$A


Instance Count

1



Total Size

16


phantomRefAfterCleanUp.hprof(localhost:8004/histo/) — in case the reference.clear()  method is not called.

Note that we have a total size greater than the previous examples (16 bytes whilst it was originally 8). This is because we used a non-static inner class, which has reference to the outer class. For more information about object retained spaces, you can check this post.

From the jhat result, we see that instance is still occupying a space even after the GC is run. This is because the first GC makes the referent phantom reachable and lets you do a clean-up operation by getting the reference from the queue.

After cleaning up the reference with the reference.clear method, if we run the GC, we will see that the object is really cleaned up from memory.

If we run the same class with JDK 11, we will see that the object is already cleared when we get the reference from the reference queue (without calling the reference.clear() method). We can now see the contents of hprof file using visualVM, as jhat is removed starting with JDK 9.

If we look at the result of  phantomRefAfterGC.hprof, we see that the referent is cleared even though a clear method is not called explicitly, unlike the results from JDK 8.

This tells us that with any JDK version greater than 8, we might now use the phantom reference even for loading a large object when we can get the previous object reference, because, at that time, it is removed from memory. But be sure to check the memory snapshot for your environment and that the object is really removed.

phantomRefAfterGC.hprof(with jdk11)

phantomRefAfterGC.hprof(with jdk11)

Note that with JDK 11, our reflection code will get the following warning. This implies that the incoming releases' reflection might not work anymore. 

WARNING: An illegal reflective access operation has occurred

WARNING: Illegal reflective access by references.PhantomRefTest (file:/myblog/out/production/java/) to field java.lang.ref.Reference.referent

WARNING: Please consider reporting this to the maintainers of references.PhantomRefTest

WARNING: Use to enable warnings of further illegal reflective access operations

WARNING: All illegal access operations will be denied in a future release

Recap

We see different types of references in Java. An ordinary reference is a strong reference, which is the default type for all Java objects. Weak and soft references help to control how objects are garbage-collected while phantom reference helps to do some clean-up operation when GC decides to remove an object from memory.

You can find the source code from this post here.

Further Reading

Different Types of References in Java

Java Garbage Collector and Reference Types

Topics:
java ,finalization ,garbage collection in java ,memory leaks ,weakhashmap ,weakreference ,phantomreference

Published at DZone with permission of Ali Gelenler . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}