javagarbage-collectionweak-referencesphantom-reference

Meaning of ReferenceQueue


I try to understand class ReferenceQueue

It is optional constructor argument for

SoftReference

and

WeakReference

Also it is mandatory argument for PhantomReference.

According information I have read I can write some thesises

a)for PhantomReference method get always returns null

b) for Phantom references:
1. gc detect that object can be deleted from memory
2. reference to the object puted to the ReferenceQueue
when we invoke clear or link to reference from queue becaom unreachable and gc see that 3. finalize methods invokes
4. free memory
for weak/soft references:
1. gc detect that object can be deleted from memory
2. finalize methods invokes
3. free memory
4. reference to the object puted to the queue

  1. When can I pass second argument to XXXReference constructor?
  2. Which help I can get?
  3. Why PhantomReference has not constructor without ReferenceQueue ?
  4. What the reason to have ReferenceQuee which get methods returns null always?

Solution

  • Maybe, the following program helps a bit:

    public class SimpleGCExample {
        public static void main(String[] args) throws InterruptedException {
            ReferenceQueue<Object> queue=new ReferenceQueue<>();
            SimpleGCExample e = new SimpleGCExample();
            Reference<Object> pRef=new PhantomReference<>(e, queue),
                              wRef=new WeakReference<>(e, queue);
            e = null;
            for(int count=0, collected=0; collected<2; ) {
                Reference ref=queue.remove(100);
                if(ref==null) {
                    System.gc();
                    count++;
                }
                else {
                    collected++;
                    System.out.println((ref==wRef? "weak": "phantom")
                                      +" reference enqueued after "+count+" gc polls");
                }
            }
        }
    
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalizing the object in "+Thread.currentThread());
            Thread.sleep(100);
            System.out.println("done finalizing.");
        }
    }
    

    On my system, it prints

    weak reference enqueued after 1 gc polls
    finalizing the object in Thread[Finalizer,8,system]
    done finalizing.
    phantom reference enqueued after 2 gc polls
    

    or

    finalizing the object in Thread[Finalizer,8,system]
    weak reference enqueued after 1 gc polls
    done finalizing.
    phantom reference enqueued after 2 gc polls
    

    The order of the first two messages occasionally differs due to the multi-threading. And sometimes, the phantom reference is reported to be enqueued after three polls, indicating that it took more than the specified 100 milliseconds.

    The key point is

    Since more than 99% of all objects don’t need finalization, all JVM vendors are strongly encouraged to detect when finalize() has not been overridden or is “trivial”, i.e. an empty method or a sole super.finalize() call. In these cases, the finalization step should be elided. You can easily check that this optimization happens in your JVM, by removing the finalize() method in the above example. Then it prints

    weak reference enqueued after 1 gc polls
    phantom reference enqueued after 1 gc polls
    

    Since both are enqueued at once and retrieved in an arbitrary order, the order of the two messages may differ. But they are always both enqueued after one gc cycle.

    It’s worth noting that the fact, that phantom references are not automatically cleared, implies that it takes another garbage collection cycle until the object’s memory really can be reused, so the above example requires at least three cycles with the non-trivial finalize() method and two without. Java 9 is going to change that, clearing phantom references automatically, so in the above example it will take two cycles with finalization and one without until the memory really can be reclaimed. Well, to be precise, in this simple example the object’s memory will never be reclaimed as the program terminates before that can happen.


    The code above also demonstrates one of the intended use cases of the Reference API. We can use it to detect when an object’s reachability changed within code under our full control, e.g. using a loop within the main method. In contrast, finalize() may be called by a different, unspecified thread at an arbitrary time. The example also shows that you can draw information from the reference object without needing the get() method.

    Practical applications often use subclasses of the reference classes to add more information to them. This is what happens with WeakHashMap.Entry which extends WeakReference and remembers the hash code and value. The cleanup can be done within the normal map operations, not needing any thread synchronization. This would not be possible with a finalize() method, besides the fact that the map implementation can’t push a finalize() method into the class of the keys.

    This is meant with the “more flexible than finalization” term.

    The WeakHashMap demonstrates how the get() method can be useful. As long as the key hasn’t been collected, it will be reported as being in the map and can be retrieved when iterating over all keys or entries.

    The PhantomReference.get() method has been overwritten to always return null to prevent that an application can resurrect the referent of an enqueued reference. This is a direct consequence of the “phantom references are not automatically cleared” rule. That rule itself is questionable and it’s original intention lies in the dark. While the rule is about to be changed in the next Java version, I’m afraid that get() will continue to always return null to be backwards compatible.