reference

After jdk1.2, Java expanded on the concept of references. References are classified into four types: strong references, soft references, weak references, and virtual references.

  • Strong references: Often referred to as references, reference assignments that are common in program code. In garbage collection, the current object is never collected as long as the strong reference exists.
  • Soft reference: a reference relationship that is one level weaker than a strong reference. Objects associated with soft references are garbage collected before the system runs out of memory.
  • Weak references: Weaker than soft references. Objects associated with weak references live only until the next garbage collection.
  • Virtual references: A virtual reference is the weakest type of reference, also known as a ghost or phantom reference. The existence of a virtual reference does not affect the existence of an object, and it is not possible to obtain an object instance through a virtual reference.

Phantom reference

As stated above, the existence of a virtual object has no effect on the survival of the object, and there is no way to obtain an object instance by virtual reference.

So, what is the use of virtual references?

Create a virtual reference need to use Java. Lang. Ref. PhantomReference.

Let’s start with his note:

/**
 * Phantom reference objects, which are enqueued after the collector
 * determines that their referents may otherwise be reclaimed.  Phantom
 * references are most often used for scheduling pre-mortem cleanup actions in* a more flexible way than is possible with the Java finalization mechanism. * * <p> If the garbage collector determines  at a certain pointin time that the
 * referent of a phantom reference is <a
 * href="package-summary.html#reachability">phantom reachable</a>, thenat that * time or at some later time it will enqueue the reference. * * <p> In order to ensure that a reclaimable object  remains so, the referent of * a phantom reference may not be retrieved: The <code>get</code> method of a * phantom reference always returns <code>null</code>. * * <p> Unlike soft and weak references, phantom references are not * automatically cleared by the garbage collector as they are enqueued. An * object that is reachable via phantom references will remain so until all * such references are cleared or themselves become * * @author Mark Reinhold * @since 1.2 */Copy the code

Objects associated with a virtual reference are added to a queue when the collector confirms collection. Often used to customize the cleanup of objects before recycling in a flexible way.

If the GC has determined that the virtual reference object is virtual reachable at a particular point in time, it is added to the queue.

To ensure that the reclaimable remains the same, an object instance cannot be obtained by a virtual reference: the get method of a virtual reference always returns NULL.

Unlike soft and weak references, virtual references are not automatically cleared when they are queued. A virtual reference is not cleared until all such references have been cleared or are themselves inaccessible.

PhantomReference comments as many mentioned a reference queue, and he is Java. Lang. Ref. ReferenceQueue.

ReferenceQueue

Let’s focus on ReferenceQueue

The easiest way to learn about a class is to look at its comments:

/**
 * Reference queues, to which registered reference objects are appended by the
 * garbage collector after the appropriate reachability changes are detected.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */
Copy the code

The reference queue to which the garbage collector adds the registered reference object after detecting the appropriate reachability change.

Let’s take a look at the queue entry code in ReferenceQueue:

 boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
            // Check that since getting the lock this reference has not already been
            // enqueued (and even thenremoved) ReferenceQueue<? > queue = r.queue;if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true; }}Copy the code

Enqueue: Enqueue: enqueue: enqueue: enqueue: enqueue: enqueue: enqueue: enqueue: enqueue

In this case, the queue property in the Reference object should point to the ReferenceQueue object (assert queue == this) :

In the enqueue method, set the queue status in reference to ENQUEUED. Since the queue is null, execute the haed and next attributes on the reference object, and then increment queueLength by 1. The results are as follows:

If there is another request to join the queue, the next pointer should be pointed to the element at the head of the queue pointed to by the current head, and the head pointer should be updated to the current reference, as shown in the figure below:

As you can see from the diagram above, the referenceQueue in Java actually implements a first-in, first-out queue using the next pointer in Reference.

In addition, during enqueueing, use the following code to ensure that only objects registered to the current queue can enter the current referenceQueue instance:

ReferenceQueue<? > queue = r.queue;if ((queue == NULL) || (queue == ENQUEUED)) {
        return false;
    }
    assert queue == this;
Copy the code

So, how do you register a reference object in a reference queue?

In fact, we have seen from the code that whether a reference is registered in a queue is determined by whether the current reference holds a reference to the corresponding queue.

Reference

Enter the abstract java.lang.ref.Reference class and, as usual, look at the comments first:

/**
 * Abstract base class for reference objects.  This class defines the
 * operations common to all reference objects.  Because reference objects are
 * implemented in close cooperation with the garbage collector, this class may
 * not be subclassed directly.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */
Copy the code

As an abstract base class for the reference object, this class defines some common operations in all references. Because reference objects are closely related to GC, this class has no direct parent.

According to the comments, Reference has four internal states:

  • Active

    The newly created instance is in the Active state.

  • Pending

    The reference queue was entered. Procedure Of course, an instance that is not registered in the queue will never be in this state.

  • Enqueued

    The reference queue has been added. Procedure Similarly, an instance that is not registered in the queue will never be in this state.

  • Inactive

    The final state, which is never changed as long as an instance is in this state.

Below I draw a less technical state diagram to show the relationship between the four states:

In Reference, the JDK uses a static block to start the Reference Handler thread:

static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for(ThreadGroup tgn = tg; tgn ! = null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg,"Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false); }}); }Copy the code

As you can see, the ReferenceHandler thread is a daemon with a priority of MAX_PRIORITY, where the ReferenceHandler is a private static inner class in Reference.

The ReferenceHandler class inherits Thread and implements an infinite loop in the run method:

private static class ReferenceHandler extends Thread { private static void ensureClassInitialized(Class<? > clazz) { try { Class.forName(clazz.getName(),true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            // pre-load and initialize InterruptedException and Cleaner classes
            // so that we don't get into trouble later in the run loop if there's
            // memory shortage while loading/initializing them lazily.
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                tryHandlePending(true); }}}Copy the code

To understand the logic in the Reference Handler thread, let’s first look at several key member properties in Reference:

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
    transient private Reference<T> discovered;  /* used by VM */
Copy the code

Discovered, the transient variable is private, and no value is assigned to it anywhere. The comment also states that it is for the virtual machine, so it seems that the virtual machine will assign it a value in the appropriate state of reference.

    /* Object used to synchronize with the garbage collector.  The collector
     * must acquire this lock at the beginning of each collection cycle.  It is
     * therefore critical that any code holding this lock complete as quickly
     * as possible, allocate no new objects, and avoid calling user code.
     */
    static private class Lock { }
    private static Lock lock = new Lock();
Copy the code

Lock an object for gc synchronization. The collector must acquire the lock during each collection cycle, so any code that needs to hold the lock should be completed as quickly as possible, with as little object allocation and user code invocation as possible.

    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null;
Copy the code

Pending, the list of references that are waiting to be queued (the next pointer is maintained in Reference, so I say list here). The collector adds references to the list, and the reference-Handler thread removes them from the list. The list uses the lock object above as the lock and uses the discovered variable to link its elements.

TryHandlePending (); tryHandlePending (); tryHandlePending (); tryHandlePending ();

    /**
     * Try handle pending {@link Reference} if there is one.<p>
     * Return {@code true} as a hint that there might be another
     * {@link Reference} pending or {@code false} when there are no more pending
     * {@link Reference}s at the moment and the program can do some other
     * useful work instead of looping.
     *
     * @param waitForNotify if {@code true} and there was no pending
     *                      {@link Reference}, wait until notified from VM
     *                      or interrupted; if {@code false}, return immediately
     *                      when there is no pending {@link Reference}.
     * @return {@code true} if there was a {@link Reference} pending and it
     *         was processed, or we waited for notification and either got it
     *         or thread was interrupted before being notified;
     *         {@code false} otherwise.
     */
Copy the code

A reference, if any, to handle pending state. If the method returns true, there are other pending references; false is the opposite. Programs can use this identifier to determine whether to enter a loop.

The main code is as follows (excluding exception handling logic) :

    Reference<Object> r;
    Cleaner c;
    synchronized (lock) {
            if(pending ! = null) { r = pending; //'instanceof' might throw OutOfMemoryError sometimes
                // so do this before un-linking 'r' from the 'pending' chain...
                c = r instanceof Cleaner ? (Cleaner) r : null;
                // unlink 'r' from 'pending' chain
                pending = r.discovered;
                r.discovered = null;
            } else {
                // The waiting on the lock may cause an OutOfMemoryError
                // because it may try to allocate exception objects.
                if (waitForNotify) {
                    lock.wait();
                }
                // retry if waited
                return waitForNotify; }} / /... Omit exception handling... / /... Omit exception handling... // Fast pathfor cleaners
    if(c ! = null) { c.clean();return true;
    }
    
    ReferenceQueue<? super Object> q = r.queue;
    if(q ! = ReferenceQueue.NULL) q.enqueue(r);return true;
    
Copy the code

Based on pending variables and discovered comments, you can plot the state of the object in memory at this time:

(Note: referenceQueue is not the same thing as referenceQueue) pending list

The process by which an object is created, registered to the reference queue, and finally added to the reference queue;

Four internal states of reference;

Pending list, background thread reference-handler processing logic;

In the end, also see sun.misc.Cleaner class, in DirectByteBuffer, is to use Cleaner to complete the cleaning of the heap memory.

The resources

In-depth understanding of Java virtual machine JDK8 related source code & annotationsCopy the code