preface

It is well known that Finalize mechanism has various defects. Therefore, in Java9, Finalize mechanism is finally abandoned and replaced by Java.lang.ref. Cleaner, which is lighter and more robust than Finalize. For those of you in this room, you might be wondering, “This is Java9 stuff. I don’t even use Java8 stuff on My Android. I don’t care about Java9 stuff.” Don’t worry, just listen to me slowly. This Cleaner in fact very early existence in JDK, has been secretly used by JDK internal, hidden so everyone is not familiar with how. Our Android implemented the NativeAllocationRegistry as early as 8.0, which is used to recycle Native memory, such as the release of bitmap local pixel data. In Java9, the Cleaner has been reconstructed and released publicly. The implementation of this version is a good example of using PhantomReference. I believe that after reading this article, you can also make your own Native Source Cleaner according to gourd and gourd.

Simple to use

Cleaner started out as an API provided inside the JDK, under the Sun.misc package. Starting with Java9, the class was refaced.the new implementation was java.lang.ref.cleaner, and the original Cleaner implementation was moved to JDK.internal.ref.cleaner.

sun.misc.Cleaner

To use Sun.misc.cleaner, you first need to wrap the cleaning code into a Runnable object:

public class CleanNativeTask implements Runnable {
    private long ptr = 0;

    public CleanNativeTask(long ptr) {
        this.ptr = ptr;
    }

    @Override
    public void run(a) {
        System.out.println("runing CleanNativeTask");

        if(address ! =0) { GetUsafeInstance.getUnsafeInstance().freeMemory(ptr); }}}Copy the code

A Cleaner object is then constructed to associate the Java objects that need to be monitored with the Runnable objects that host the cleaning code. When Java objects are collected by GC, the code in Runnable is automatically executed.

public class ObjectInHeapUseCleaner {
    private long ptr = 0;

    public ObjectInHeapUseCleaner(a) {
        ptr = GetUsafeInstance.getUnsafeInstance().allocateMemory(2 * 1024 * 1024);
    }

    public static void main(String[] args) {  
        while (true) {
            System.gc();
            ObjectInHeapUseCleaner heap = new ObjectInHeapUseCleaner();
            Cleaner.create(heap, newCleanNativeTask(heap.ptr)); }}}Copy the code

The Runnable of the cleanup action should not be implemented as a non-static inner class /lambda. This is because non-static inner classes tend to hold references to external classes, preventing a monitored object from becoming a phantom reachable object and influencing the cleanup action.

java.lang.ref.Cleaner

Java.lang.ref. Cleaner requires the creation of a Cleaner object before it is used. When creating a Cleaner object, a ThreadFactory object can be passed to it to specify the thread of execution for cleaning the code. Similarly, cleanup code needs to be wrapped in Runnable. The Cleaner object’s register method is then called to associate the target object with the cleaning action.

public class CleaningExample {
    private static final Cleaner cleaner = Cleaner.create();

    public CleaningExample(a) {
        ObjectInHeapUseCleaner heap = new ObjectInHeapUseCleaner();
        this.cleanable = cleaner.register(this.newFreeMemoryTask(heap.ptr)); }}Copy the code

The basic principle of

Phantom reference PhantomReference

Cleaner is used to synchronize the recovery of the corresponding Native memory when Java objects are GC, so it is necessary to track the recovery time of Java objects (the referent is used to refer to the tracked Java objects below). The Cleaner uses phantom references to track the referent’s life cycle. The following figure shows the processing of virtual references:

During GC, the referent is not reclaimed when held by strong references. When the referent is only held by the virtual reference, it can be reclaimed by the GC and the virtual reference is added to the specified ReferenceQueue for subsequent processing.

The realization of the sun. The misc. The Cleaner

Reference is handled differently on ART and JVM, so Sun.misc.cleaner is implemented slightly differently on both platforms, but the design idea is the same. The code for the JVM platform presented in this article is based on JDK12, at this time sun.misc.cleaner has been moved to the JDK.internal. Ref package.

The Cleaner directly inherits from PhantomReference and provides a clean method for indirectly calling clean code.

// ART: [libcore/ojluni/src/main/java/sun/misc/Cleaner.java](https://cs.android.com/android/platform/superproject/+/master:libco re/ojluni/src/main/java/sun/misc/Cleaner.java)
// JDK: jdk/internal/ref/Cleaner.java

public class Cleaner
    extends PhantomReference<Object>
{...private final Runnable thunk; // Wrap the cleanup code Runnable
    
    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue);
        this.thunk = thunk;
    }
    
    public static Cleaner create(Object ob, Runnable thunk) {
        if (thunk == null)
            return null;
        return add(newCleaner(ob, thunk)); }...public void clean(a) {
        if(! remove(this))
            return;
        try {
            thunk.run();
        } catch (finalThrowable x) { ... }}}Copy the code

When we create a Cleaner for referent, we create a virtual reference for it. Therefore, when the Referent object is GC, the Cleaner is sensed, and unlike PhantomReference, the cleaner is not added to the ReferenceQueue, it is executed during enlistment.

// ART: [libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java](https://cs.android.com/android/platform/superproject/+/ Android - 11.0.0 _r1: libcore ojluni/SRC/main/Java/Java/lang/ref/ReferenceQueue. Java; l=77; DRC = android - 11.0.0 _r1)

public class ReferenceQueue<T> {...private boolean enqueueLocked(Reference<? extends T> r) {
        if(r.queueNext ! =null) {
            return false;
        }

        if (r instanceof Cleaner) {
            // If the Reference is Cleaner, call the clean method directly.
            Cleaner cl = (sun.misc.Cleaner) r;
            cl.clean();

            r.queueNext = sQueueNextUnenqueued;
            return true; }... }... }Copy the code
// JDK12: java/lang/ref/Reference.java

private static void processPendingReferences(a) {
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        while(pendingList ! =null) {
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;

            if (ref instanceofCleaner) { ((Cleaner)ref).clean(); . }else {
                ReferenceQueue<? super Object> q = ref.queue;
                if(q ! = ReferenceQueue.NULL) q.enqueue(ref); }}... }Copy the code

As can be seen, Cleaner is treated as a special object in the ReferenceQueue process, and the clean method is called before joining the team. Therefore, when using Sun.misc.Cleaner, it is necessary to ensure that the clean method is fast, in case of blocking the processing of other references.

Java. Lang. Ref. The realization of the Cleaner

In java.lang.ref.Cleaner, the Cleaner no longer directly inherits PhantomReference to track and clean the referent object, but Cleanable, It will replace Cleaner as a subclass of PhantomReference. The new Cleaner object is a manager that manages all Cleanable objects registered with it and is responsible for executing their cleaning code on a specified thread.

// JDK: java/lang/ref/Cleaner.java

public final class Cleaner {...public static Cleaner create(a) {
        Cleaner cleaner = new Cleaner();
        cleaner.impl.start(cleaner, null);
        return cleaner;
    }

    ThreadFactory is used to specify the thread of execution for cleaning code
    public static Cleaner create(ThreadFactory threadFactory) {
        Cleaner cleaner = new Cleaner();
        cleaner.impl.start(cleaner, threadFactory);
        return cleaner;
    }

    // Register the monitored object and the corresponding cleanup code
    public Cleanable register(Object obj, Runnable action) {
        return new CleanerImpl.PhantomCleanableRef(obj, this, action); }}Copy the code

Unlike Sun.misc. Cleaner, Cleanable is not treated specially in the ReferenceQueue process, it goes into the designated ReferenceQueue like any other reference. This reference queue is created in the CleanerImpl, which is then created along with it as a Cleaner member.

// JDK: java/lang/ref/Cleaner.java

public final class Cleaner {
    final CleanerImpl impl;
    private Cleaner(a) {
        impl = newCleanerImpl(); }... }Copy the code
// JDK: jdk/internal/ref/CleanerImpl.java

public final class CleanerImpl implements Runnable {
    final ReferenceQueue<Object> queue;
    public CleanerImpl(a) {
        queue = newReferenceQueue<>(); . }... }Copy the code

ReferenceQueue in CleanerImpl is then used to initialize the PhantomReference when a Cleanable object is created using the Cleaner.register method, This is the ReferenceQueue that the virtual reference goes into when the referent is reclaimed.

// JDK: jdk/internal/ref/PhantomCleanable.java

public abstract class PhantomCleanable<T> extends PhantomReference<T>
        implements Cleaner.Cleanable {
    public PhantomCleanable(T referent, Cleaner cleaner) {
        super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue);
        this.list = CleanerImpl.getCleanerImpl(cleaner).phantomCleanableList; insert(); . }}Copy the code

When Cleanable enters the queue, the corresponding referent has been GC. At this point, the cleanup thread in the CleanerImpl will retrieve the Cleanable object and then actively call its clean method, which in turn indirectly executes the cleanup code encapsulated in Runnable.

// JDK: jdk/internal/ref/CleanerImpl.java

public final class CleanerImpl implements Runnable {...// Start the cleaning thread, which is executed when the Cleaner object is created
    public void start(Cleaner cleaner, ThreadFactory threadFactory) {...if (threadFactory == null) {
            threadFactory = CleanerImpl.InnocuousThreadFactory.factory();
        }
        Thread thread = threadFactory.newThread(this);
        thread.setDaemon(true);
        thread.start();
    }

    public void run(a) {...while(! phantomCleanableList.isListEmpty() || ! weakCleanableList.isListEmpty() || ! softCleanableList.isListEmpty()) { ...try {
                Cleanable ref = (Cleanable) queue.remove(60 * 1000L);
                if(ref ! =null) { ref.clean(); }}catch(Throwable e) { ... }}}}Copy the code

Java.lang.ref. Cleaner uses PhantomReference’s ReferenceQueue to separate the execution of clean code from the GC process, which can avoid improper use of the entire reference processing process, and also allows developers to specify the execution thread of clean code flexibly. Relative to sun. Misc.Cleaner will be more flexible, a bit safer.

Compared to Finalize

  1. The Cleaner uses virtual references to track the life cycle of a target reference, and the cleaning code is separate from the target reference, so it does not affect the GC of the target reference.
  2. Finalize the throwunchecked exceptionsIs not aware of (will not print the abnormal stack through the log), Cleaner does not exist this problem.
  3. Finalizerthreads usually have a lower priority than application threads and raise OOM if they are not enqueued as quickly as they are enqueued. Cleaner can control the thread of Cleaner mechanism by itself, which is a little better than Finalize.

reference

  • ART view | Finalize a replacement for the Cleaner – LuHang ART view | Finalize a replacement for the Cleaner

  • ReferenceQueue and Reference source code analysis (ii) — Sword52888 【JAVA Reference】ReferenceQueue and Reference source code analysis (ii) _Sword52888 blog -CSDN blog