1. Reference type (excluding strong reference)
It can be understood that the direct subclass of Reference is processed by JVM, so it has no effect in directly inheriting the Reference type in the code. It can only be inherited from its subclass. The corresponding subclass types include the following. (Ignore those that are not used in Java, such as jnireference)
SoftReference
WeakReference
FinalReference
PhantomReference
The above reference type is also mentioned in the corresponding javadoc. FinalReference is specially designed for the finalize method, and there are several other specific application scenarios. Among them, softReference is used in memory-related caches, weakReference is used in most scenarios related to recycling. phantomReference is used in callback scenarios for packaging object recycling (such as resource leakage detection).
You can directly view the subclass information of several types in the ide to understand what scenarios are used in most frameworks by inheriting the corresponding types, so that we can actually perform type selection processing.
2. Reference constructor
It provides two constructors internally, one with a queue and the other without a queue. The meaning of queue is that we can monitor this queue externally. That is, if an object is about to be recycled, the corresponding reference object will be placed in this queue. When we get the reference, we can do some more transactions.
If you don't, you can only train the reference object continuously, and judge whether the get inside returns null (the phantomReference object cannot do this, the get always returns null, so it only has a constructor with a queue). Both methods have corresponding usage scenarios, depending on the actual application. For example, in weakHashMap, you choose to query the queue data to determine whether any object will be recycled. For ThreadLocalMap, it is used to determine whether get() is null for processing.
The corresponding constructor is as follows:
Reference(T reference) { this(referent, null);} Reference(T reference, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}The NULL queue here can be understood as a queue that does not require any processing of the data in its queue. And it will not access any data inside it.
In the above object, the reference represents the object it references, that is, the object we need to be wrapped in when we construct it. The definition that the object is about to be recycled means that this object has no other reference except for reference (not that it is not really not referenced, but the gcRoot accessibility is unreachable to avoid the problem of circular reference).
A queue is the queue to be notified when the object is recycled. When the object is recycled, the entire reference object (not the recycled object) will be placed in the queue, and then external programs can obtain the corresponding data by monitoring this queue.
3. ReferenceQueue and Reference Reference Chain
The queue here is nominally a queue, but there is no actual storage structure inside. Its storage depends on the relationship between internal nodes. It can be understood that queue is a structure similar to a linked list, and the node here is actually the reference itself. It can be understood that queue is a container for a linked list, which only stores the current head node, and the subsequent nodes are maintained by each reference node themselves through next.
Reference status value
Each reference object has a corresponding state description, that is, it describes itself and the wrapped object currently in, for the convenience of querying, positioning or processing.
1. Active: Active state, that is, the corresponding object is a strong reference state and has not been recycled. In this state, the object will not be placed in the queue. In this state, next is null, and the queue is the queue referenced when it is defined.
2. Pending: Prepare to put it in the queue. In this state, the objects to be processed will be queued one by one into the queue. During this time window, the corresponding objects are in the pending state. No matter what reference, if you enter this state, you can think that in the corresponding state, next is itself (set by jvm), and the queue is the queue referenced when it is defined.
3. Enqueued: The corresponding object is already to be recycled, and the corresponding reference object has been placed in the queue. The external thread is ready to query the queue to obtain the corresponding data. In this state, next is the next object to be processed, and the queue is the special identification object ENQUEUED.
4. Inactive: That is, this object has been retrieved from the queue from the outside and has been processed. That means that this reference object can be recycled, and the internally encapsulated object can also be recycled (the actual recycling operation depends on whether the clear action is called). It can be understood that those entering this state must be recycled.
jvm does not need to define the state value to determine which state the corresponding reference is in. It only needs to calculate next and queue to make judgments.
4. ReferenceQueue#head
Always save the latest node to be processed in the current queue. You can consider the queue as a last-in first-out queue. When a new node enters, the following logic is adopted.
newE.next = head;head=newE;
Then, when obtaining, use the corresponding logic
tmp = head;head=tmp.next;return tmp;
V. Reference#next
That is, describe the next node stored by the reference node that is about to be processed. But next will only make sense when placed in the queue. In order to describe the corresponding status value, after being placed in the queue, its queue will no longer refer to the queue. Instead, it will refer to a special ENQUEUED. Because it has been placed in the queue, it will not be placed in the queue again.
6. Reference#referent
That is, describe the actual object referenced by the current reference, which will be processed carefully as stated in the annotation. That is, when this thing will be recycled, and if it is recycled, it will be directly set to null, and external programs can learn from the reference object itself (rather than referent) that the recycling behavior occurs.
7. ReferenceQueue#enqueue pending reference enqueue
This process is the process of the reference object from active->pending->enqued. This method is to process the object that handles the pending state as an enqued state. The corresponding process is the previous logic, that is, a node is enqued, and the corresponding code is as follows.
r.queue = ENQUEUED;r.next = (head == null) ? r : head;head = r;queueLength++;lock.notifyAll();
The last nitify means notifying the external program that blocks on the current queue before. (That is, the pending object has not been obtained before)
8. Reference#tryHandlePending
That is, it handles the change of the reference object from active to pending state. Inside the Reference object, there is a static field, and its corresponding declaration is as follows:
/* 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;
It can be understood that jvm will put the object to be processed on this static field when gc. At the same time, another field discovered represents the next object of the object to be processed. That is, it can be understood that the object to be processed is also a linked list. It is queued through discovery. You only need to keep getting pending, and then continuously get the next object through discovery. Because both threads may access this pending object, it is necessary to use lock processing.
The corresponding processing process is as follows:
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;} // enqueue the processing object, that is, it enters the enqued state ReferenceQueue<? super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);9. Reference#clear
Clear the original object referenced by the reference object, so that the original object can no longer be accessed through the get() method. From the corresponding design idea, since it has entered the queue object, it means that the corresponding object needs to be recycled because there is no need to access the original object again. This method will not be called by the JVM, and jvm directly clears the corresponding reference through field operations, and its specific implementation is consistent with the current method.
The semantics of clear is to null the reference.
After the WeakReference object enters the queue, the corresponding reference is null.
SoftReference object, if the object does not enter the queue when memory is sufficient, the corresponding reference will not be null. If it needs to be processed (not enough memory or other policies), the corresponding reference is set to null and then enter the queue.
FinalReference object, because it needs to call its finalize object, even if its reference is entered into a queue, its reference will not be null, that is, it will not be cleared.
The PhantomReference object, because the get itself implemented to return null, is not very useful. Because it will not be cleared regardless of whether it is enqueue or not.
10. ReferenceHandler enqueue thread
As mentioned above, jvm will set the object to be processed into the pending object, so there must be a thread to perform continuous enqueue operations. This thread refers to the processor thread, and its priority is MAX_PRIORITY, that is, the highest. The corresponding startup process is created static initialization, which can be understood as when any Reference object or class is used, this thread will be created and started. The corresponding code is as follows:
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();}Its priority is the highest, which can be understood as the need to continuously process reference objects. When printing a running thread through jstack, the corresponding Reference Handler refers to the thread initialized here, as shown below:
11. JVM related
In each of the above processing points, they are related to the JVM recycling process. That is, it is believed that the gc process will work in conjunction with the corresponding reference. For example, using the cms collector, the preclean process is involved in the entire process mentioned above, and the softReference remark processing, etc. At the same time, various processing of the reference object also needs to be related to the specific type. The corresponding JVM processing uses C++ code, so it needs to be carefully sorted out.
12. Summary
Like the finalReference object, the entire reference and referenceQueue are a group of processing groups that work together. In order to ensure different reference semantics, the process related to jvm gc is finally realized for different scenarios and different reference levels.
In addition, because directly using referenceQueue and opening threads to monitor queues is too troublesome and complicated. You can refer to FinalizableReferenceQueue implemented by Google guava and the corresponding FinalizableReference object. The processing process can be simplified a little bit. The above is the entire content of this article, and I hope it will bring some help to everyone's learning or work.