menu

Saturday, August 24, 2019

Understanding Type of References in Java

Introduction:

       There are four types of references in Java and for each of them garbage collector behaves differently.

- Strong References
- Weak References
- Soft References
- Phantom References

Below I will try to explain each type of reference's characteristics and possible use cases.
  
Strong References:

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


Let's see a strong reference behaviour with an example.
Below StrongReference class has a nested class named A. It creates an instance of A and set 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 jhat command line utility. Remember to delete the previous dump files with deleteOldDumps method otherwise heap dump will give "File exists" error.
We use this class to use HotSpotDiagnostic mbean and dump memory to a hprof file.
Note that the second parameter of dumpHeap method is a boolean and if we set it true it will only dump live objects which will cause running GC before taking heap dump so that we do not need to hint with System.gc(). In this example we set it the second parameter to false and try to hint GC to run with System.gc() instead.
Note that to monitor GC runs with System.gc() call, run the class with "-verbose:gc" JVM parameter so that we can see the GC running in logs.
We take 3 dumps file before setting the object to null, after setting to null and after hinting to run GC.
Now let's run the class and follow the steps below using jhat command to see our memory snapshot through web interface.
- Run "jhat -J-Xmx1g -port 8000 strongRefBeforeGCEligible.hprof"  with different ports for all 3 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..
And the web interface of jhat we will see the below 3 results for 3 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 reclaimed by GC.
  
Weak References:

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

There is a get() method of WeakReference which returns 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 which will be used in the application for a while, if you want to keep some additional information, you generally use a global map with temporary object as key and additional information as value. This can be seen as a in-memory cache. With a strong referenced map, even if the temporary object lifecycle ends, since we a 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 memory leak and it can be prevented using Weak references. Fortunately we do not need to implement this as java has a built-in WeakHashMap class and 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 creation of Weak reference. This queue will hold the reference object (not the referent) after GC runs. We can poll the reference after the referent garbage collected. Before GC collect 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 set to null before GC and 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 map is zero. This is because there is a expungeStaleEntries method in 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 has to find and remove the Entry object from the map itself which in turn let the value of the entry to be garbage collected in the next GC cycle (In above example it is a B object). It is more effective than running through all map and remove an entry if 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, and 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 after the only reference to it is a weak reference as we see from the below jhat results.

And again the web interface of jhat we will see the below 3 results for 3 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 java.lang.ref.SoftReference class. A soft reference does garbage collected only if there is not enough memory left in 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 possibly not get that much advantage from using a cache as an almost full memory will make our application already 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
                               softRefAfterGC.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 cleanup actions before GC remove the object. 
It mainly used for a replacement for finalize method which is unreliable and can slow down the application as JVM uses separate thread pool for finalization and an object with a finalize method consumes more resources than a normal object. 
Another problem with 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 first GC makes the object finalizable and the second GC reclaimed object if it is not resurrected. And between this two GC cycles finalizer thread must have been run to actually reclaim the object. If the finalizer thread does not run, more than two number 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 enqueud to the reference queue by garbage collector after it determines that the referent object is phantom reachable that is 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, phantom reference get method always return 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 clear method to clear it or the reference itself being 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 of using 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 cleanup action could be determining the time to load a heavy object after another one 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 queue we cannot be sure the object really claimed in terms of memory. Even after java 9 seeing the reference in reference queue does not tell us certainly that the space that object is using really claimed. Instead it means the space hold 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 see an example usage of PhantomReference below where we do a cleanup operation.
Note that we used 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 force the phantom reference monitoring thread to finish before the application exit. For that purpose we used a CountDownLatch in shutdown hook which is a barrier allowing us to wait another thread to finish.
Finally, in the monitoring thread we read the reference from reference queue, do the cleanup operation with the information we set during the creation of reference. Note that we need to call the clear method of reference after we finish clean up so that the referent will be set to null and ready to clean up by GC. If we do not call clean method on reference we will see the referent is still there. See the shutdown hook thread code which 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 not resurrection of phantom reference).
If you run the same code with a jdk version 9 or greater you will see that the referent is automatically set to null and you do not need to call 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 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 the monitoring thread to exit so that no code interrupted but finished as it should finish.

The web interface of jhat we will see the below 4 results for 4 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/)
                               (for the case reference.clear() method not called)

Note that we have a total size greater than the previous examples (16 bytes whils it was 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 jhat result we see that instance is still occupy space even after GC run. Because the first GC makes the referent phantom reachable and let you do cleanup operation by getting the reference from the queue. After cleaning up the reference by reference.clear method, if we run GC we will see that the object is really cleaned up from memory.

If we run the same class with jdk11, we will see that the object is already cleared when we get the reference from 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 jdk9. If we look at the result of phantomRefAfterGC.hprof, wee see that the referent is cleared even though clear method not called explicitly unlike the results of jdk8.

This tells us that with 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 as at that time it is removed from memory. But be sure checking the memory snapshot for your environment to be sure the object is really removed.



phantomRefAfterGC.hprof(with jdk11)

Note that with jdk 11 our reflection code will get the below warning. This implies in coming 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 --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release


Conclusion:

       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 and object from memory. You can find the source code for this post here.


References: