WeakHashMap

definition

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
}

WeakHashMap implements the Map interface, which has one more reference queue than HashMap:

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

In order to understand the principle of WeakHashMap, we must first understand the reference queue of ReferenceQueue and the referenced object Reference:

Reference Types in Java

 Strong Reference: Strong reference is the default reference type used by instantiated objects in Java, such as "Object o = new Object()". As long as the strong reference exists, the garbage collector will not collect it drop the referenced object.

Soft Reference (SoftReference) : Soft references are used to describe objects that are still useful but not required. For objects pointed to by soft references, the system will reclaim the memory occupied by these objects before memory overflow occurs. If there is still not enough memory after the reclamation, a memory overflow exception will be thrown.

Weak reference (WeakReference) : Weak reference is also used to describe non-essential objects, but its strength is weaker than soft reference. The object pointed to by weak reference can only survive until the next garbage collection occurs, once the garbage collector starts Work, regardless of whether the current memory is sufficient, the object pointed to by the weak reference will be reclaimed.

Phantom Reference (PhantomReference) : Phantom reference is the weakest reference relationship. Whether or not an object has a phantom reference has absolutely no effect on whether it will be reclaimed, and we can't even get an instance of an object through a phantom reference. The only use of setting a phantom reference is to receive a system notification when the object is reclaimed.

Among them, SoftReference, WeakReference, and PhantomReference are all subclasses of Reference.

 

Reference source code analysis

Reference is the abstract base class for reference objects. This class defines operations that are common to all referenced objects. Because reference objects are implemented in close cooperation with the garbage collector, you cannot subclass this class directly.

Reference provides two constructors:

Reference(T referent) {
    this(referent, null);
}

Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

One constructor takes a reference queue that needs to be registered, and one does not. The meaning of the queue is that we can know whether the actual object pointed to by the reference instance has been reclaimed by operating the queue from the outside, and we can also perform some additional operations on the reference instance through the queue; but if our reference The instance does not specify a reference queue when it is created, so if we want to know whether the actual object is recycled, we can only keep polling whether the get() method of the reference instance is empty. It is worth noting that the virtual reference PhantomReference, because its get() method always returns null, its constructor must specify a reference queue. These two methods of querying whether the actual object is recycled have applications. For example, weakHashMap chooses to query the data of the queue to determine whether there is an object to be recycled; and ThreadLocalMap is used to determine whether the get() is null for processing. .

Next are its main members:

private T referent;      

Here we first clarify some nouns, the Reference class is also called a reference class, and its instance Reference Instance is a reference instance, but because it is an abstract class, its instance can only be a subclass soft (soft) reference, weak ( Weak) reference, one of the virtual (phantom) references, as for the object referenced by the reference instance, we call it the actual object (that is, the referent we wrote above).

volatile ReferenceQueue<? super T> queue;    /* Reference object queue */

The queue is the reference queue registered by the current reference instance. Once the reachability of the actual object changes appropriately, the reference instance will be added to the queue.

* The state is encoded in the queue and next fields as follows:
*
*     Active: queue = ReferenceQueue with which instance is registered, or
*     ReferenceQueue.NULL if it was not registered with a queue; next =
*     null.
*
*     Pending: queue = ReferenceQueue with which instance is registered;
*     next = Following instance in queue, or this if at end of list.
*
*     Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
*     in queue, or this if at end of list.
*
*     Inactive: queue = ReferenceQueue.NULL; next = this.
*
Reference next;

next is used to represent the next reference instance to be processed of the current reference instance. The four states we see in the comments are the internal states of the reference instance and cannot be viewed externally or modified directly:

 

  • Active: The newly created reference instance is in the Active state, but when the GC detects some appropriate change in the reachability of the actual object referenced by the instance (the actual object is unreachable for GC roots), its state will be based on this Whether the instance is registered in the reference queue and becomes Pending or Inactive.

  • Pending: When a reference instance is placed in the pending-Reference list, it is in the Pending state. At this point, the instance is waiting for a thread called Reference-handler to enqueue this instance. If a reference instance is not registered in a reference queue, the instance will never enter the Pending state.

  • Enqueued: A reference instance is in the Enqueued state when it is added to the reference queue in which it is registered. When a reference instance is removed from the reference queue, the instance will change from the Enqueued state to the Inactive state. If a reference instance is not registered in a reference queue, the instance will never enter the Enqueued state.

  • Inactive: Once a reference instance is in the Inactive state, its state will no longer change, and it means that the actual object pointed to by the reference instance will definitely be reclaimed by the GC.

 

In fact, the Reference class does not explicitly define internal state values. The JVM only needs to use the values ​​of the members queue and next to determine which state the current reference instance is in:

  • Active: queue is the instance of ReferenceQueue passed in when creating the reference instance or ReferenceQueue.NULL; next is null

  • Pending: queue is the instance of ReferenceQueue passed in when creating the reference instance; next is this

  • Enqueued: queue is ReferenceQueue.ENQUEUED; next is the next instance in the queue that needs to be processed or this if the instance is the last one in the queue

  • Inactive: queue is ReferenceQueue.NULL; next is this

The method provided by Reference is relatively simple:

 
public T get() {
   return this.referent;
}

The get() method simply returns the actual object referenced by the reference instance, or null if the object is recycled or the reference instance is cleared.

public void clear() {
  this.referent = null;
}

Calling this method does not cause this object to be enqueued. This method is only called by Java code; when the garbage collector clears the reference, it executes directly without calling this method. 
The clear method essentially sets the referent to null and clears the actual object referenced by the reference instance, so that the actual object can no longer be accessed through the get() method.

public boolean isEnqueued() {
  return (this.queue == ReferenceQueue.ENQUEUED);
}

Whether this reference instance has been put into the queue is determined by whether the reference queue instance is equal to ReferenceQueue.ENQUEUED.

public boolean enqueue() {
  return this.queue.enqueue(this);
}

The enqueue() method can manually add reference instances to the reference queue.

ReferenceQueue source code analysis

ReferenceQueue is the reference queue to which the garbage collector adds registered reference objects after detecting an appropriate reachability change.

ReferenceQueue implements the enqueue and poll of the queue, and the internal elements are the Reference objects mentioned above. The storage structure of queue elements is single-chain storage, relying on the next field of each reference object to find the next element.

The main members are:

private  volatile Reference extends T> head = null;

Used to store the node that currently needs to be processed.

static ReferenceQueue NULL = new Null<>();
static ReferenceQueue ENQUEUED = new Null<>();

The static variables NULL and ENQUEUED are used to represent an empty queue that does not provide a default reference queue and a queue that has performed an enqueue operation, respectively.

Enqueue logic:

 
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ 
    synchronized (r) {
         // Check whether reference has already performed enqueue operation 
        if (r.queue == ENQUEUED) return  false ;
         synchronized (lock) {
             // Set the member queue of the reference instance to ENQUEUED 
            r.queue = ENQUEUED;
             // If the head node is empty, it means that the reference instance is the first element in the queue, and its next instance is equal to this
             / / If the head node is not empty, point its next instance to the element pointed to by the head node 
            r.next = (head == null ) ? r : head;
             // The head node points to the current reference instance
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
}

Simply put, the enqueue operation is to place the reference instance that needs to be enqueued each time at the position of the head node, and point its next field to the old head node element. So the entire ReferenceQueue is a LIFO data structure.

The logic for dequeuing is:

private Reference<? extends T> reallyPoll() {       /* Must hold lock */
    if (head != null) {
        Reference <? extends T> r = head;
         // The head node points to null, if there is only one element in the queue; otherwise it points to r.next 
        head = (r.next == r) ? null : r.next;
         // The head node The element's queue points to ReferenceQueue.NULL 
        r.queue = NULL;
         // point r.next to this 
        r.next = r;
        queueLength--;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(-1);
        }
        return r;
    }
    return null;
}

In general, the role of ReferenceQueue is the middle layer between JAVA GC and Reference reference objects. We can take external processing operations through ReferenceQueue in time according to the changes in the reachability status of the monitored objects.

 

Comparing the source code of WeakHashMap and HashMap, it is found that the implementation of the methods in WeakHashMap is basically the same as that of HashMap. The biggest difference lies in the method expungeStaleEntries(), which is the essence of the entire WeakHashMap, which we will explain later. 
It uses weak references as a storage scheme for internal data. WeakHashMap is a typical application of weak references.

 Key source code analysis of WeakHashMap

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    // The rest of the code is omitted 
}

You can see that the Entry inheritance extends the WeakReference class. And in its constructor, a weak reference to the key is constructed. In addition, in the various operations of WeakHashMap, such as get(), put(), size(), the expungeStaleEntries() method is called indirectly or directly to clean up the appearance of keys holding weak references. 

 The implementation of the expungeStaleEntries() method is as follows:

private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

It can be seen that every time the expungeStaleEntries() method is called, it will look for a cleared key object in the reference queue. If there is, find its value in the table, and set the value to null, and the next pointer is also set to null. Let the GC recycle these resources.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325368803&siteId=291194637