We all know that in JVM garbage collection, the GC uses reference counting and reachability algorithms to determine whether an object instance or data in the heap is garbage.
Whether the reference count algorithm is used to determine the number of references to an object or whether the reference chain of the object is reachable by the root search algorithm, whether the object is alive or not is related to “references”.
GitHub JavaKeeper, N line Internet development essential skills weapon spectrum, have you want.
reference
Let’s start with references, references in Java, similar to Pointers in C. When we first learned Java, we learned that Java data types fall into two broad categories, primitive types and reference types.
Primitive type: The least granular data type built into the programming language. It includes four broad categories and eight types:
- Four integer types: byte, short, int, and long
- Two floating point types: float, double
- One character type: char
- 1 Boolean type: Boolean
Reference type: A reference type refers to an object, not a primitive value. Variables that refer to an object are reference variables. In Java, all types except basic types are reference types, which mainly include: class, interface, array, enumeration, annotation
With datatypes, the JVM normalizes the management of program data, which is stored in different formats and locations for different data types
Back to the point, by reference, you can operate on objects in the heap. To quote a passage from Java Programming Ideas,
“Each programming language has its own way of handling data. Sometimes programmers have to be careful about what type of data they are dealing with. Do you manipulate elements directly, or do you manipulate objects with some kind of indirect representation based on special syntax (such as Pointers in C/C++)? All of this is simplified in Java, where everything is treated as an object. Therefore, we can adopt a uniform grammar. Although everything is “treated” as an object, the manipulated identifier is really a “reference” to an object.
Such as:
Person person = new Person("Zhang");
Copy the code
The person in this case is a reference to the Person instance “Sam”, which is how we operate the “Sam” instance.
Prior to JDK 1.2, references were traditionally defined in Java: If the value stored in a reference data type represented the starting address of another piece of memory, the refrence data was said to represent a reference to a piece of memory or an object. This definition is very pure, but too narrow, an object under this definition can only be referred to or not referred to two states, for how to describe some “tasteless” objects, it is a pity to abandon it.
For example, we want to describe objects that can remain in memory when there is enough space; If the memory is still stressed after garbage collection, these objects can be discarded. Many system cache functions fit this application scenario.
After JDK 1.2, Java expanded the concept of references to classify them as
- Strong Reference
- Soft Reference
- Weak Reference
- Phantom Reference
The intensity of these four citations gradually diminishes in turn.
The purpose of the four references introduced in Java is to allow the program to determine the life cycle of the object, and the JVM uses the garbage collector to treat these four references differently to change the life cycle of the object.
UML diagrams in JDK 8
The FinalReference class is visible within the package, and the other three reference types are public and can be used directly in your application.
Strong reference
The most common in Java is strong references, where an object is assigned to a reference variable that is a strong reference. References like “Object obj = new Object()”.
When an object is referenced by a strongly referenced variable, it is in a reachable state and cannot be collected by the garbage collector, even if the object is never used or collected.
When the JVM runs out of memory, it starts garbage collection, and for strongly referenced objects, it does not collect them, even if they are in OOM. ** So strong references are sometimes a cause of Java memory leaks.
An ordinary object that has no other reference relationship is generally considered recyclable by the garbage collector as long as the reference scope is exceeded or the corresponding (strong) reference is explicitly assigned to null. (The exact timing depends on the garbage collection strategy).
coding~
public class StrongRefenenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = o1;
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(o2); //java.lang.Object@2503dbd3}}Copy the code
In the demo, although O1 has been collected, O2 strongly references O1 and is always present, so it will not be collected by GC
Soft references
Soft references is a relatively strong reference to weaken some of reference, need to use Java. Lang. Ref. SoftReference class to implement, can let the object save some garbage collection.
Soft references are used to describe objects that are useful but not necessary. Objects associated with soft references are listed in the recycle scope and recycled a second time before the system is about to run out of memory. An out-of-memory exception is thrown if there is still not enough memory left for this collection.
For soft-reference only objects, it is not reclaimed when the system is out of memory, but is reclaimed when the system is out of memory.
coding~
//VM options: -Xms5m -Xmx5m
public class SoftRefenenceDemo {
public static void main(String[] args) {
softRefMemoryEnough();
System.out.println("------ Out of memory ------");
softRefMemoryNotEnough();
}
private static void softRefMemoryEnough(a) {
Object o1 = new Object();
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(s1.get());
}
/** * JVM configuration '-xms5m -xmx5m', then deliberately new a large object, so that there is no memory generated OOM, see soft reference collection */
private static void softRefMemoryNotEnough(a) {
Object o1 = new Object();
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
o1 = null;
byte[] bytes = new byte[10 * 1024 * 1024]; System.out.println(o1); System.out.println(s1.get()); }}Copy the code
Output
java.lang.Object@2503dbd3 java.lang.Object@2503dbd3 null java.lang.Object@2503dbd3 ------ Specifies whether the memory is insufficient java.lang.Object@4b67cf4d java.lang.Object@4b67cf4d java.lang.OutOfMemoryError: Java heap space at reference.SoftRefenenceDemo.softRefMemoryNotEnough(SoftRefenenceDemo.java:42) at reference.SoftRefenenceDemo.main(SoftRefenenceDemo.java:15) null nullCopy the code
Soft references are usually used in memory-sensitive programs, such as caches, where memory is reserved and reclaimed when it is insufficient.
Let’s take a look at the soft references used by Mybatis cache class SoftCache
public Object getObject(Object key) {
Object result = null;
SoftReference<Object> softReference = (SoftReference)this.delegate.getObject(key);
if(softReference ! =null) {
result = softReference.get();
if (result == null) {
this.delegate.removeObject(key);
} else {
synchronized(this.hardLinksToAvoidGarbageCollection) {
this.hardLinksToAvoidGarbageCollection.addFirst(result);
if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
this.hardLinksToAvoidGarbageCollection.removeLast(); }}}}return result;
}
Copy the code
A weak reference
Weak references are also used to describe non-essential objects, but they are weaker than soft references, and objects associated with weak references only survive until the next garbage collection occurs. When the garbage collector works, objects associated only with weak references are reclaimed regardless of whether there is currently enough memory.
Weak references need to use Java. Lang. Ref. WeakReference class, its survival period shorter than soft references.
For weakly referenced objects, the garbage collection mechanism reclaims the object’s memory as soon as it runs, regardless of whether the JVM has enough memory.
coding~
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> w1 = new WeakReference<Object>(o1);
System.out.println(o1);
System.out.println(w1.get());
o1 = null; System.gc(); System.out.println(o1); System.out.println(w1.get()); }}Copy the code
Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.
Weak references are often used to implement normalized mappings, such as WeakHashMap in the JDK
Interviewer: Since you know weak references, what about WeakHashMap
public class WeakHashMapDemo {
public static void main(String[] args) throws InterruptedException {
myHashMap();
myWeakHashMap();
}
public static void myHashMap(a) {
HashMap<String, String> map = new HashMap<String, String>();
String key = new String("k1");
String value = "v1";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
public static void myWeakHashMap(a) throws InterruptedException {
WeakHashMap<String, String> map = new WeakHashMap<String, String>();
//String key = "weak";
// I just started writing the above code
// What would happen if I wrote it like this? That's not a quote
String key = new String("weak");
String value = "map";
map.put(key, value);
System.out.println(map);
// Remove strong references
key = null;
System.gc();
Thread.sleep(1000); System.out.println(map); }}Copy the code
Let’s look at the weak references used in ThreadLocal
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/ /...
}
Copy the code
Phantom reference
Virtual references, also known as “ghost references” or “phantom references,” are the weakest type of reference relationship.
A virtual reference, as its name implies, is a virtual reference, which is different from other kinds of references. The existence of a virtual reference does not affect the lifetime of an object, nor can an object instance be obtained through a virtual reference.
Phantom reference to Java. Lang. Ref. PhantomReference.
If an object holds only a virtual reference, it is as likely to be collected by the garbage collector at any time as if it had no reference at all. It cannot be used alone or accessed through it. The virtual reference must be used in conjunction with the reference queue.
The main purpose of virtual references is to track the status of object garbage collection. It just provides a mechanism to make sure that objects do something when they are finalize.
PhantomReference’s get method always returns NULL, so the corresponding reference object cannot be accessed. The meaning is to indicate that an object has entered the finalization stage and can be collected by the GC for more flexible collection operations than finalization mechanisms.
In other words, the only purpose of setting up a virtual reference is to receive a system notification or add further processing when the object is collected by the collector.
Java allows you to use the Finalize () method to do the necessary cleanup before the garbage collector purifies objects from memory.
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1,referenceQueue);
System.out.println(o1);
System.out.println(referenceQueue.poll());
System.out.println(phantomReference.get());
o1 = null;
System.gc();
Thread.sleep(3000);
System.out.println(o1);
System.out.println(referenceQueue.poll()); // reference queueSystem.out.println(phantomReference.get()); }}Copy the code
java.lang.Object@4554617c
null
null
null
java.lang.ref.PhantomReference@74a14482
null
Copy the code
Reference queue
ReferenceQueue is used to work with references and can work without it.
SoftReference, WeakReference, and PhantomReference all have a constructor that can pass ReferenceQueue.
When a reference is created, you specify the associated queue to which it will be added when the GC frees the object’s memory. If a program finds that a virtual reference has been added to the reference queue, it can take the necessary action before the memory of the referenced object is reclaimed, which acts as a notification mechanism.
When there is data in the associated reference queue, it means that the object in the pointed heap memory is reclaimed. In this way, the JVM allows us to do whatever we want after the object is destroyed.
Finally, take a look at the implementation in the source code
The Reference source (JDK8)
We have a general understanding of the four types of strong, weak and weak references, and we also know that there is no corresponding type representation except for strong references, which is universal. The remaining three references are direct subclasses of java.lang.ref.Reference.
Can we customize the Reference type by inheriting Reference?
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.
Reference is the base class for all Reference objects. This class defines common operations for all reference objects. Because the reference object is implemented in close collaboration with the garbage collector, the class may not be directly subclassed.
The four states of Reference
- Active: The newly created reference instance is in the Active state, but its state changes to when GC detects some change in the reacability of the actual object referenced by the instance (the actual object is unreachable by GC roots)
Pending
orInactive
. If Reference registers ReferenceQueue, it is switched toPending
And Reference will joinpending-Reference
In the linked list, if the ReferenceQueue is not registered, it is switched toInactive
. - Pending: When a Reference instance is placed in a pending-reference list, it is in a Pending state. At this point, the instance is waiting for a thread called Reference-handler to enqueue the instance. If a reference instance is not registered in a reference queue, the instance will never enter a Pending state
- Enqueued: The state of a Reference in the ReferenceQueue queue, entered if the Reference is removed from the queue
Inactive
state - Inactive: Once a reference instance is in Inactive state, its state will not change and the actual object to which the reference instance refers must be reclaimed by GC
Reference
Constructor and member variables of
public abstract class Reference<T> {
// Reference the object to which it points
private T referent;
// When reference is reclaimed, the current reference instance is added to the queue
volatile ReferenceQueue<? super T> queue;
// A Reference to the next Reference instance through which the Reference instance constructs a one-way linked list
volatile Reference next;
// Is modified by transient, which represents the next object to be processed in a different list based on state, mainly the next element in the pending-reference list. Assignment is called directly by the JVM
private transient Reference<T> discovered;
// A list of references to be queued.
// Since GC detects that the actual object to which a reference instance points is unreachable, it points pending to that reference instance,
// The discovered field indicates the next instance to be processed, so we simply keep assigning the discovered instance to pending after processing the current pending. So pending is like a linked list.
private static Reference<Object> pending = null;
/* -- Constructors -- */
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null)? ReferenceQueue.NULL : queue; }}Copy the code
Reference provides two constructors, one with the ReferenceQueue and one without.
The significance of ReferenceQueue is that we can know whether the actual object that the reference instance points to is reclaimed from the outside through the operation of ReferenceQueue. At the same time, we can also carry out some additional operations on the reference instance through the ReferenceQueue. But if our reference instance is created without specifying a reference queue, we can only keep polling the reference instance’s get() method to see if the actual object is reclaimed.
Note the virtual reference PhantomReference, whose get() method always returns NULL, so its constructor must specify a reference queue.
Both of these methods are applied to query whether the actual object is recovered. For example, WeakHashMap chooses to query the data of queue to determine whether any object will be recovered. ThreadLocalMap, on the other hand, checks whether get() is null.
Instance methods (methods unrelated to the ReferenceHandler thread)
private static Lock lock = new Lock();
// Get the referent instance held
public T get(a) {
return this.referent;
}
// Set the referent instance to null
public void clear(a) {
this.referent = null;
}
// Check whether the state is enQEUED
public boolean isEnqueued(a) {
return (this.queue == ReferenceQueue.ENQUEUED);
}
// 入队参数,同时会把referent置为null
public boolean enqueue(a) {
return this.queue.enqueue(this);
}
Copy the code
ReferenceHandler thread
From the above discussion, we know that the status of a Reference is Active after its instantiation. After its referenced object is reclaimed, the garbage collector adds it to the pending-Reference list and waits for it to be added to the ReferenceQueue.
ReferenceHandler thread is a thread established and run by Reference static code block. Its running method relies on more native methods. The main function of the ReferenceHandler thread is to add a reference instance from a Pending list to a reference queue and point pending to the next reference instance.
// Control garbage collector operations and Pending state Reference enqueue operations do not conflict with the global lock executed
// The garbage collector acquires this lock before starting a garbage collection cycle
// So all code that hogs the lock must be done as soon as possible. No new objects can be generated, and no user code can be called
static private class Lock {}private static Lock lock = new Lock();
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) newNoClassDefFoundError(e.getMessage()).initCause(e); }}static {
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run(a) {
while (true) {
tryHandlePending(true); }}}static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
// Check whether there is any data in the pending-reference list
if(pending ! =null) {
// If there is a Pending Reference, fetch it from the list
r = pending;
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// If there is no Pending Reference, call wait
if (waitForNotify) {
lock.wait();
}
// retry if waited
returnwaitForNotify; }}}catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
// Fast path for cleaners
if(c ! =null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if(q ! = ReferenceQueue.NULL) q.enqueue(r);return true;
}
// The ReferenceHandler thread is started in the static block of Reference
static {
ThreadGroup inherits the ThreadGroup of the current thread of execution (usually the main thread)
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for(ThreadGroup tgn = tg; tgn ! =null;
tg = tgn, tgn = tg.getParent());
// Create a thread instance, name it Reference Handler, configure the highest priority and background running (daemon thread), and start
Thread handler = new ReferenceHandler(tg, "Reference Handler");
// The ReferenceHandler thread has the highest priority
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference(a) {
return tryHandlePending(false); }}); }Copy the code
Since the ReferenceHandler thread is created by Reference’s static code, it will be created and run whenever Reference’s parent class is initialized, and since it is a daemon thread, unless the JVM process terminates, Otherwise it will always run in the background (note the use of an infinite loop in its run() method).
ReferenceQueue source
public class ReferenceQueue<T> {
public ReferenceQueue(a) {}// The inner Null class inherits from ReferenceQueue and overrides enqueue to return false
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false; }}// Indicates that the Queue is not registered
static ReferenceQueue<Object> NULL = new Null<>();
// Indicates that the Queue is already in the Queue
static ReferenceQueue<Object> ENQUEUED = new Null<>();
// Static inner class as lock object
static private class Lock {};/* Mutex used to synchronize the enqueue of ReferenceHandler with the remove and poll exit operations of user thread operations */
private Lock lock = new Lock();
// Reference the head node of the list
private volatile Reference<? extends T> head = null;
// Reference the queue length, which is increased by 1 when joining the queue and decreased by 1 when leaving the queue
private long queueLength = 0;
// The enqueue operation will only be called by the Reference instance
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// If the referencequeue. NULL or referencequeue. ENQUEUED is held by the referencequeue. ENQUEUED, false is returned for queueing failureReferenceQueue<? > queue = r.queue;if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// the current reference instance is ENQUEUED, so its own ReferenceQueue instance is set to ReferenceQueue.ENQUEUED
r.queue = ENQUEUED;
// If the list has no elements, the reference instance is directly used as the head node, otherwise the previous reference instance is used as the next node
r.next = (head == null)? r : head;// The current instance is updated as a head node, that is, each new reference instance is used as a head node, and the existing reference instance is used as a successor node
head = r;
// The queue length increases by 1
queueLength++;
// Special processing FinalReference, VM count
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
// Wake up all waiting threads
lock.notifyAll();
return true; }}// reference the poll operation of the queue. This method must be called in the case of locking
private Reference<? extends T> reallyPoll() { /* Must hold lock */
Reference<? extends T> r = head;
if(r ! =null) {
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
// Update the next node as the head node. If the next node is itself, null is returned
head = (rn == r) ? null : rn;
r.queue = NULL;
// The current head node is changed to a circular queue to take into account that FinalReference is inactive and to avoid the problem of duplicate queues
r.next = r;
// The queue length is reduced by 1
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
// The public poll operation of the queue is called reallyPoll after locking
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
returnreallyPoll(); }}// Removing the next reference element in the reference queue also relies on the blocking mechanism provided by objects in reallyPoll
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException
{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPoll();
if(r ! =null) return r;
long start = (timeout == 0)?0 : System.nanoTime();
for (;;) {
lock.wait(timeout);
r = reallyPoll();
if(r ! =null) return r;
if(timeout ! =0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000 _000;
if (timeout <= 0) return null; start = end; }}}}public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
void forEach(Consumer<? super Reference<? extends T>> action) {
for(Reference<? extends T> r = head; r ! =null;) {
action.accept(r);
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
if (rn == r) {
if (r.queue == ENQUEUED) {
// still enqueued -> we reached end of chain
r = null;
} else {
// already dequeued: r.queue == NULL; ->
// restart from head when overtaken by queue poller(s)r = head; }}else {
// next in chainr = rn; }}}}Copy the code
ReferenceQueue stores only the head nodes of the Reference linked list. All nodes of the real Reference linked list are stored in the Reference instance itself and spliced by the attribute next. ReferenceQueue provides operations such as enqueue, poll and remove for Reference linked list
Reference and thanks
Juejin. Cn/post / 684490…
www.kdgregory.com/index.php?p…
Blog.csdn.net/Jesministra…
Throwable. Club / 2019/02/16 /…
In-depth Understanding of the Java Virtual Machine