Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)

preface

  • The Java Reference type is closely related to the virtual machine garbage collection mechanism, and is also one of the important interview questions. Java is generally believed to have four types of references (strong, soft, weak, and virtual), but there is a fifth type of Reference hidden in Java. Do you know what it is?
  • In this article, I will summarize the usage & differences of reference types and analyze the source code based on the ART virtual machine. Please be sure to like and follow if you can help, it really means a lot to me.

Tip: This article source analysis based on Android 9.0 ART virtual machine.


directory


1. An overview of the

1.1 What is a Citation?

In Java, the basic definition of a reference is the starting address of an object/chunk of memory, similar to the definition of a pointer in C/C++. Since JDK 1.2, Java has expanded the types of references into four types based on the strength of the reference: strong & soft & Weak & virtual.

1.2 Functions of References

Many articles fail to make it clear that different reference types have different effects. Soft & weak references provide the ability to control object lifetimes more flexibly, while virtual references provide the ability to be aware of object garbage collection. In addition to virtual references, Object# Finalize () also provides the ability to sense objects being garbage collected. In Section 5, I will analyze the principles and differences between the two.

Reference types Class role Object GC timing (regardless of GC policy)
Strong reference There is no / GC Root reachable will not be collected
Soft references SoftReference Flexible control of survival When free memory is insufficient to allocate new objects
A weak reference WeakReference Flexible control of survival Every time the GC
Phantom reference PhantomReference Aware object garbage collection Every time the GC

Tip: Whether or not an object is GC depends not only on the reference type, but also on the policy that the current GC uses.

1.3 Object Access and Location Modes

According to the reference to access objects, divided into the handle access & direct Pointers to visit two ways, you can see before I wrote an article: “the Java | Object obj = new Object () how many bytes?”


2. Reference & reference queues

In this section, we first analyze the source code of Reference & Reference queue to sort out the basic dependency relationship between them.

Note again: this article source analysis based on Android 9.0 ART virtual machine.

2.1 Reference source code analysis

Reference is an abstract class with four subclasses:

  • SoftReference (SoftReference)
  • WeakReference (WeakReference)
  • PhantomReference
  • FinalizerReference (@ hide)

The fourth FinalizerReference is the @hide hidden class, which I’ll cover in Section 4. First of all, we will first analyze the source of the Reference class:

Reference.java

Public abstract class Reference<T> {1, constructor Reference(T referent) {this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = queue; } 2.1 Reference to the object private T referent; If the object is reclaimed, return null public T get() {return getReferent(); Public void clear() {clearReferent(); } 3. Final ReferenceQueue<? super T> queue; Question: What do these two variables do? Reference queueNext; Reference<? > pendingNext; private final native T getReferent(); native void clearReferent(); . }Copy the code

This source code is not complex, mainly concerned with the following points:

  • When creating a reference object, you can specify the associated ReferenceQueue. The default value is NULL.
  • 2,referentIs the object to which the reference points;
  • 3,queueIs the associated reference queue;
  • 4,queueNext & pendingNextI am inIn section 2.2To speak.

As you can see, getting the object to which the reference points and clearing the reference relation are both calls to native methods:

java_lang_ref_Reference.cc

static jobject Reference_getReferent(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); ObjPtr<mirror::Reference> ref = soa.Decode<mirror::Reference>(javaThis); ObjPtr<mirror::Object> const referent = Runtime::Current()->GetHeap()->GetReferenceProcessor()->GetReferent(soa.Self(), ref); return soa.AddLocalReference<jobject>(referent); } static void Reference_clearReferent(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); ObjPtr<mirror::Reference> ref = soa.Decode<mirror::Reference>(javaThis); Clear the ReferenceProcessor Runtime::Current()->GetHeap()->GetReferenceProcessor()->ClearReferent(ref); }Copy the code

The ReferenceProcessor is a module in ART that is specially used to process Reference objects. I will mention it again later. Also, for PhantomReference, the get() method always returns NULL.

PhantomReference.java

public T get() {
    return null;
}
Copy the code

2.2 ReferenceQueue source code analysis

ReferenceQueue (ReferenceQueue) needs to match soft reference, weak reference and virtual reference, source code is as follows:

ReferenceQueue.java

public class ReferenceQueue<T> { private Reference<? extends T> head = null; private Reference<? extends T> tail = null; Public ReferenceQueue() {} Boolean enqueue(Reference<? extends T> reference) { synchronized (lock) { if (enqueueLocked(reference)) { lock.notifyAll(); return true; } return false; }} private Boolean enqueueLocked(Reference<? extends T> r) { .... } public Reference<? extends T> poll() { ... }}Copy the code

ReferenceQueue is a queue based on a single linked list. I won’t post the implementation details inside the method. It’s not important.

Here we focus on the following methods:

  • ReferenceQueue.add(…)

ReferenceQueue.add(…) Static method, source code as follows:

public static Reference<? > unenqueued = null; Static void add(Reference<? > list) {synchronized (ReferenceQueue. Class) {if (unenqueued == null) { Unenqueued = list; } else {2.1 Find unenqueued Reference<? > last = unenqueued; while (last.pendingNext ! = unenqueued) { last = last.pendingNext; } 2.2 Append a reference to the end of unenqueued last.pendingNext = list; last = list; while (last.pendingNext ! = list) { last = last.pendingNext; } last.pendingNext = unenqueued; } 3, wake up wait ReferenceQueue. Class thread lock ReferenceQueue. Class. The notifyAll (); }}Copy the code

As you can see, this method simply appends the parameter Reference object to the end of unenQueued. Notice that appending the object to the tail also wakes up the thread waiting for the Referencequeue.class lock. Where is this thread? I’ll do that in verse 3.

  • ReferenceQueue.enqueuePending(…)

ReferenceQueue.enqueuePending(…) Static method, source code as follows:

Public static void enqueuePending(Reference<? > list) { Reference<? > start = list; Do {ReferenceQueue queue = list.queue; If (queue == null) {1, skip Reference<? > next = list.pendingNext; list.pendingNext = list; list = next; PendingNext synchronized (queue. Lock) {2.1 do {Reference<? > next = list.pendingNext; list.pendingNext = list; The team queue. EnqueueLocked (list); list = next; } while (list ! = start && list.queue == queue); 2.2 Wake up queue.lock. NotifyAll (); } } } while (list ! = start); }Copy the code

This method simply adds the reference object to the associated reference queue and then wakes up the thread waiting for the lock on queue.lock.

2.3 summary

Seeing this, let’s first summarize the content of this section and the questions encountered:

  • 1. When creating a reference object, the reference is associated with the reference queue, which is based on a single linked list;
  • 2, static method referencequyue.add (…) Append the parameter Reference object to the unenQueued tail, then wake up the thread waiting for the ReferenceQueue. Class lock;
  • 3, the static method ReferenceQueue. EnqueuePending (…). The reference object is added to the associated reference queue, and the thread waiting for the lock on queue.lock is subsequently woken up.

So where are these waiting threads?


3. Daemon threads

Daemon threads are started when the virtual machine starts:

runtime.cc

Void the Runtime: : StartDaemonThreads () {invokes the Java. Lang. Daemons. The start () Thread * self = Thread: : Current (); JNIEnv* env = self->GetJniEnv(); env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons, WellKnownClasses::java_lang_Daemons_start); }Copy the code

Daemons.java

Public static void the start () {launched four daemon thread ReferenceQueueDaemon. The INSTANCE. The start (); FinalizerDaemon.INSTANCE.start(); FinalizerWatchdogDaemon.INSTANCE.start(); HeapTaskDaemon.INSTANCE.start(); } private static abstract class Daemon implements Runnable { private Thread thread; private String name; protected Daemon(String name) { this.name = name; } public synchronized void start() { startInternal(); } public void startInternal() { thread = new Thread(ThreadGroup.systemThreadGroup, this, name); thread.setDaemon(true); thread.start(); } public void run() { runInternal(); } public abstract void runInternal(); protected synchronized boolean isRunning() { return thread ! = null; }}Copy the code

Daemon is an abstract subclass of Runnable. Its four implementation classes are ReferenceQueueDaemon, FinalizerDaemon, FinalizerWatchdogDaemon, and HeapTaskDaemon.

Citing the weread.qq.com/web/reader/… — by Deng Fanping

3.1 ReferenceQueueDaemon thread

private static class ReferenceQueueDaemon extends Daemon { private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); ReferenceQueueDaemon() { super("ReferenceQueueDaemon"); } @Override public void runInternal() { while (isRunning()) { Reference<? > list; 1, synchronization, synchronized (ReferenceQueue class) {2, check - wait for a while (ReferenceQueue. Unenqueued = = null) { ReferenceQueue.class.wait(); } list = ReferenceQueue.unenqueued; ReferenceQueue.unenqueued = null; } 3, add the object reference queue ReferenceQueue. EnqueuePending (list); }}}Copy the code

As you can see, is the main purpose of ReferenceQueueDaemon thread polling judgment ReferenceQueue. Unenqueued for null, if no empty, call in the previous section about ReferenceQueue. EnqueuePending (…). .

Tip: “check – wait” “set – wake”, this is the typical guard pause mode.

3.2 FinalizerDaemon thread

Private static class FinalizerDaemon extends Daemon {private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); Private final ReferenceQueue<Object> queue = finalizerReference.queue; FinalizerDaemon() { super("FinalizerDaemon"); } @override public void runInternal() {while (isRunning()) {FinalizerReference<? > finalizingReference = (FinalizerReference<? >)queue.poll(); Object# Finalize() doFinalize(finalizingReference); } @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") private void doFinalize(FinalizerReference<? > reference) {2.1 remove FinalizerReference object FinalizerReference. Remove (reference); Object = reference.get(); 2.3 Clear reference.clear(); 2.4 Call Object# Finalize () object.finalize(); }}Copy the code

As you can see, the main role of the FinalizerDaemon thread is to poll to fetch references from the reference queue and execute Object# Finalize (). Note that this queue is actually a static variable of FinalizerReference. FinalizerReference is one of the subclasses of Reference mentioned in Section 2.1 (@hide), which I’ll cover in Section 4.

3.3 FinalizerWatchdogDaemon thread

Used to listen for Object# Finalize () execution time and exit the virtual machine if the execution time exceeds MAX_FINALIZE_NANOS

private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;

Os.kill(Os.getpid(), OsConstants.SIGQUIT);
Copy the code

3.4 summary

Seeing this, let’s first summarize the content of this section and the questions encountered:

  • 1, ReferenceQueueDaemon daemon thread wait ReferenceQueue. Class locks, polling judgment ReferenceQueue. Whether unenqueued is empty, If it is not empty call the ReferenceQueue. EnqueuePending (…). ;

  • FinalizerDaemon daemon waits for queue. Lock and polls to fetch references from finalizerReference. queue, execute Object# Finalize ().

So where do the references in finalizerReference.queue come from?


4. Analysis of execution principle of Finalize () function

4.1 Finalizable tag bit

When ClassLinker loads a class, the function LoadMethod(), which resolves its member methods, checks whether the method name is Finalize () and marks the class as finalizable if it is.

4.2 Creating FinalizerReference Objects

If a class is marked as finalizable, in the new object, ART virtual opportunities call Heap: AddFinalizerReference (…). :

heap.cc

void Heap::AddFinalizerReference(Thread* self, ObjPtr<mirror::Object>* object) { ScopedObjectAccess soa(self); ScopedLocalRef<jobject> arg(self->GetJniEnv(), soa.AddLocalReference<jobject>(*object)); jvalue args[1]; args[0].l = arg.get(); Invokes the Java. Lang. Ref. FinalizerReference. Add (...). InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_FinalizerReference_add, args); *object = soa.Decode<mirror::Object>(arg.get()); }Copy the code

FinalizerReference.java

public static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();

private static FinalizerReference<?> head = null;

private FinalizerReference<?> prev;
private FinalizerReference<?> next;

public static void add(Object referent) {
    FinalizerReference<?> reference = new FinalizerReference<Object>(referent, queue);
    synchronized (LIST_LOCK) {
        头插法
        reference.prev = null;
        reference.next = head;
        if (head != null) {
            head.prev = reference;
        }
        head = reference;
    }
}
Copy the code

As you can see, for every object marked as an instance of the Finalizable class, the ART virtual machine also creates a FinalizerReference object pointing to it, Add the FinalizerReference object to the FinalizerReference static member variable queue.

4.3 Garbage Collection

When the virtual machine is about to reclaim an object, it calls referencequyue.add (…) mentioned in Section 2.2 (p. :

reference_processor.cc

class ClearedReferenceTask : public HeapTask { ... InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_ReferenceQueue_add, args); . };Copy the code

4.4 Execute the Finalize () method

The source code to execute finalize() is described in section 3.2. The FinalizerDaemon thread waits for the queue.lock lock and polls to fetch the reference from finalizerReference.queue, calling Object# Finalize ().

4.5 summary

Seeing this, let’s first sum up the content of this section:

  • 1. Rewrite Object# Finalize () class, and create the associated FinalizerReference when creating objects;
  • Referencequyue.add (…) is called when the object is about to be GC. Append the reference object to the unenQueued tail and wake up the thread waiting for the ReferenceQueue. Class lock.
  • 3, ReferenceQueueDaemon daemon thread is awakened, judge ReferenceQueue. Unenqueued for null, if no empty, call ReferenceQueue. EnqueuePending (…). And wakes up the thread waiting for the queue. Lock lock;
  • The FinalizerDaemon daemon thread is woken up to fetch the reference from finalizerReference.queue and execute Object# Finalize ().


5. Aware object garbage collection

In addition to virtual references, Object# Finalize () also provides the ability to sense when objects are garbage collected, but virtual references are more elegant and have higher performance.

The main reason is that Object# Finalize () queuing is executed in the FinalizeDaemon daemon thread, since the daemon thread has a lower priority than other threads. When CPU resources are tight and daemons are competing for less CPU time slices, reference objects will pile up in the queue, increasing OOM risk and making the collection time unstable.

By contrast, with virtual references, multiple threads can be used to process the reference as needed. Or directly use PhantomReference’s subclass Cleaner is more convenient.

public class Cleaner extends PhantomReference<Object> {
    ...
}
Copy the code

6. Summary

  • Since JDK 1.2, Java has expanded the types of references. Soft & Weak references provide more flexible control over object lifetimes, and virtual references provide object garbage collection awareness.

  • Strong references are only recoverable if the object does not have a chain of references to GC Root; Soft references are not guaranteed to be collected every time a GC is collected, but only when free memory is insufficient to allocate new objects; Weak references are collected every time GC; Virtual references have nothing to do with collection timing, but provide a sense of object garbage collection;

  • FinalizerReference is also a reference type. It is a hidden class that implements the function of calling Object# Finalize () before the object is reclaimed;

  • Object# Finalize () also provides the ability to sense objects being garbage collected, but since Finalize () is executed in the daemon thread, references will accumulate in the reference queue when CPU resources are tight, which increases OOM risk and the recycling time is unstable.


The resources

  • Understanding Android in Depth: Java Virtual Machine ART (Chapter 14.8). By Fanping Deng
  • In Depth understanding the Java Virtual Machine: Advanced JVM features and Best Practices (3rd edition) (chapters 2 and 3). By Zhiming Chou
  • Algorithms and Implementation of Garbage Recycling. By [Japan] Nakamura Narayo, [Japan] Aikawa Mitsu

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!