--- old/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-07-03 16:04:57.040710895 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-07-03 16:04:56.966710382 +0200 @@ -25,85 +25,57 @@ package java.lang.ref; -import java.security.PrivilegedAction; -import java.security.AccessController; -import sun.misc.JavaLangAccess; import sun.misc.ManagedLocalsThread; import sun.misc.SharedSecrets; import sun.misc.VM; -final class Finalizer extends FinalReference { /* Package-private; must be in - same package as the Reference - class */ - - private static ReferenceQueue queue = new ReferenceQueue<>(); - private static Finalizer unfinalized = null; - private static final Object lock = new Object(); - - private Finalizer - next = null, - prev = null; - - private boolean hasBeenFinalized() { - return (next == this); - } - - private void add() { - synchronized (lock) { - if (unfinalized != null) { - this.next = unfinalized; - unfinalized.prev = this; - } - unfinalized = this; - } - } +import java.security.AccessController; +import java.security.PrivilegedAction; - private void remove() { - synchronized (lock) { - if (unfinalized == this) { - if (this.next != null) { - unfinalized = this.next; - } else { - unfinalized = this.prev; - } - } - if (this.next != null) { - this.next.prev = this.prev; - } - if (this.prev != null) { - this.prev.next = this.next; - } - this.next = this; /* Indicates that this has been finalized */ - this.prev = this; - } - } +/* Package-private; must be in same package as the Reference class */ +class Finalizer extends FinalReference implements Cleaner { - private Finalizer(Object finalizee) { - super(finalizee, queue); - add(); + Finalizer(T finalizee) { + super(finalizee, null); } - /* Invoked by VM */ + /** Invoked by VM for objects overriding finalize() method */ static void register(Object finalizee) { - new Finalizer(finalizee); + Finalizer finalizer = new Finalizer<>(finalizee); + finalizer.link(); } - private void runFinalizer(JavaLangAccess jla) { - synchronized (this) { - if (hasBeenFinalized()) return; - remove(); + @Override + public void clean() { + T finalizee = delete(); + if (finalizee == null) { + return; } + unlink(); try { - Object finalizee = this.get(); - if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { - jla.invokeFinalize(finalizee); - - /* Clear stack slot containing this variable, to decrease - the chances of false retention with a conservative GC */ - finalizee = null; + if (!(finalizee instanceof java.lang.Enum)) { + invokeFinalizee(finalizee); } } catch (Throwable x) { } - super.clear(); + finalizee = null; + } + + /* Invoke the finalize() method on the finalizee (overridden by Finalizator) */ + void invokeFinalizee(T finalizee) throws Throwable { + SharedSecrets.getJavaLangAccess().invokeFinalize(finalizee); + finalizee = null; + } + + @Override + public void clear() { + T finalizee = delete(); + if (finalizee == null) { + return; + } + unlink(); + /* Clear stack slot containing this variable, to decrease + the chances of false retention with a conservative GC */ + finalizee = null; } /* Create a privileged secondary finalizer thread in the system thread @@ -150,13 +122,8 @@ // in case of recursive call to run() if (running) return; - final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; - for (;;) { - Finalizer f = (Finalizer)queue.poll(); - if (f == null) break; - f.runFinalizer(jla); - } + ReferenceHandling.runFinalization(); } }); } @@ -173,61 +140,78 @@ // in case of recursive call to run() if (running) return; - final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; - for (;;) { - Finalizer f; - synchronized (lock) { - f = unfinalized; - if (f == null) break; - unfinalized = f.next; - } - f.runFinalizer(jla); + for (DLList ucList : uncleanedLists) + for (Reference f = ucList.first(); f != null; f = ucList.succ(f)) { + ((Cleaner)f).clean(); }}}); } - private static class FinalizerThread extends ManagedLocalsThread { - private volatile boolean running; - FinalizerThread(ThreadGroup g) { - super(g, "Finalizer"); - } - public void run() { - // in case of recursive call to run() - if (running) - return; - - // Finalizer thread starts before System.initializeSystemClass - // is called. Wait until JavaLangAccess is available - while (!VM.isBooted()) { - // delay until VM completes initialization - try { - VM.awaitBooted(); - } catch (InterruptedException x) { - // ignore and continue - } - } - final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - running = true; - for (;;) { - try { - Finalizer f = (Finalizer)queue.remove(); - f.runFinalizer(jla); - } catch (InterruptedException x) { - // ignore and continue - } - } - } + /** + * Atomically clear the Finalizer and return the finalizee. + * + * @return the finalizee or null + */ + private T delete() { + T referent = getReferentVolatile(); + return (referent != null) && casReferent(referent, null) + ? referent : null; } - static { - ThreadGroup tg = Thread.currentThread().getThreadGroup(); - for (ThreadGroup tgn = tg; - tgn != null; - tg = tgn, tgn = tg.getParent()); - Thread finalizer = new FinalizerThread(tg); - finalizer.setPriority(Thread.MAX_PRIORITY - 2); - finalizer.setDaemon(true); - finalizer.start(); + // Methods and state that enable Finalizer to be an element of DLList + + /** A constructor used for special Reference instances used in DLList */ + Finalizer(boolean setPrevToSelf, boolean setNextToSelf) { + super(null, null); + if (setPrevToSelf) prevDll = this; + if (setNextToSelf) nextDll = this; + } + + @SuppressWarnings("unused") // assigned through Unsafe + private volatile Reference prevDll, nextDll; + + boolean isDeletedDll() { + return getReferentVolatile() == null; + } + + Reference getPrevDll() { + return prevDll; + } + + void lazySetPrevDll(Reference val) { + UNSAFE.putOrderedObject(this, PREV_DLL, val); + } + + boolean casPrevDll(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, PREV_DLL, cmp, val); } + Reference getNextDll() { + return nextDll; + } + + void lazySetNextDll(Reference val) { + UNSAFE.putOrderedObject(this, NEXT_DLL, val); + } + + boolean casNextDll(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, NEXT_DLL, cmp, val); + } + + // Unsafe machinery + + private static final sun.misc.Unsafe UNSAFE; + private static final long PREV_DLL; + private static final long NEXT_DLL; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class fc = Finalizer.class; + PREV_DLL = UNSAFE.objectFieldOffset(fc.getDeclaredField("prevDll")); + NEXT_DLL = UNSAFE.objectFieldOffset(fc.getDeclaredField("nextDll")); + } catch (Exception e) { + throw new Error(e); + } + } } --- old/src/java.base/share/classes/java/lang/ref/Reference.java 2015-07-03 16:04:57.248712337 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2015-07-03 16:04:57.178711852 +0200 @@ -25,7 +25,6 @@ package java.lang.ref; -import sun.misc.Cleaner; import sun.misc.JavaLangRefAccess; import sun.misc.ManagedLocalsThread; import sun.misc.SharedSecrets; @@ -99,14 +98,13 @@ * Enqueued: next reference in queue (or this if last) * Inactive: this */ - @SuppressWarnings("rawtypes") - Reference next; + Reference next; /* When active: next element in a discovered reference list maintained by GC (or this if last) * pending: next element in the pending list (or null if last) * otherwise: NULL */ - transient private Reference discovered; /* used by VM */ + transient private Reference discovered; /* used by VM */ /* Object used to synchronize with the garbage collector. The collector @@ -115,7 +113,7 @@ * as possible, allocate no new objects, and avoid calling user code. */ static private class Lock { } - private static Lock lock = new Lock(); + private static final Lock lock = new Lock(); /* List of References waiting to be enqueued. The collector adds @@ -123,10 +121,25 @@ * them. This list is protected by the above lock object. The * list uses the discovered field to link its elements. */ - private static Reference pending = null; + private static Reference pending; - /* High-priority thread to enqueue pending References + /** + * Max. number of Reference(s) to unhook from pending chain in one chunk + * before releasing the lock, handling them and grabbing the + * lock again. + */ + private static final int UNHOOK_CHUNK_SIZE = 32768; + + /** + * Max. number of j.l.r.Cleaner(s) to execute in one ForkJoinTask */ + private static final int CLEANER_CHUNK_SIZE = 256; + + /** + * Max. number of Reference(s) to enqueue in one chunk + */ + private static final int ENQUEUE_CHUNK_SIZE = 256; + private static class ReferenceHandler extends ManagedLocalsThread { private static void ensureClassInitialized(Class clazz) { @@ -142,6 +155,7 @@ // so that we don't get into trouble later in the run loop if there's // memory shortage while loading/initializing them lazily. ensureClassInitialized(InterruptedException.class); + ensureClassInitialized(sun.misc.Cleaner.class); ensureClassInitialized(Cleaner.class); } @@ -150,95 +164,227 @@ } public void run() { + // wait for VM to boot-up before starting reference handling + // ForkJoinPool since it needs access to some system properties + // and Finalizer needs access to SharedSecrets. while (true) { - tryHandlePending(true); + try { + sun.misc.VM.awaitBooted(); + break; + } catch (InterruptedException e) { + // ignore; + } + } + // enter endless loop + boolean[] morePending = new boolean[1]; + while (true) { + Reference chunk = null; + try { + synchronized (lock) { + chunk = Reference.unhookPendingChunk(UNHOOK_CHUNK_SIZE, morePending); + if (chunk == null) { + // waiting on notification can throw InterruptedException + // if the thread is interrupted, but also OutOfMemoryError + // if the InterruptedException can not be allocated. + lock.wait(); + // since we have already re-obtained the lock, we can + // re-try poll and will typically get a non-null chunk. + chunk = Reference.unhookPendingChunk(UNHOOK_CHUNK_SIZE, morePending); + } + } + } catch (OutOfMemoryError e) { + // give other threads some time so they hopefully release some + // references and GC reclaims some space, then retry... + Thread.yield(); + } catch (InterruptedException e) { + // ignore + } + if (chunk != null) { + if (morePending[0]) { + // submit a handling task and return for next chunk + new ReferenceHandling.PendingChunkHandler(chunk).submit(); + } else { + // no more pending, so we can handle the chunk directly + Reference.handlePendingChunk(chunk); + } + } } } } /** - * Try handle pending {@link Reference} if there is one.

- * Return {@code true} as a hint that there might be another - * {@link Reference} pending or {@code false} when there are no more pending + * Try handle a chunk of pending {@link Reference}s if there are any.

+ * Return {@code true} as a hint that there are more + * {@link Reference}s pending or {@code false} when there are no more pending * {@link Reference}s at the moment and the program can do some other * useful work instead of looping. * - * @param waitForNotify if {@code true} and there was no pending - * {@link Reference}, wait until notified from VM - * or interrupted; if {@code false}, return immediately - * when there is no pending {@link Reference}. - * @return {@code true} if there was a {@link Reference} pending and it - * was processed, or we waited for notification and either got it - * or thread was interrupted before being notified; - * {@code false} otherwise. - */ - static boolean tryHandlePending(boolean waitForNotify) { - Reference r; - Cleaner c; - try { - synchronized (lock) { - 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; - } else { - // The waiting on the lock may cause an OutOfMemoryError - // because it may try to allocate exception objects. - if (waitForNotify) { - lock.wait(); + * @return {@code true} if there is be more {@link Reference}s pending. + */ + static boolean tryHandlePending() { + Reference r; + synchronized (lock) { + r = unhookPendingChunk(UNHOOK_CHUNK_SIZE, null); + } + if (r == null) return false; + handlePendingChunk(r); + synchronized (lock) { + return pending != null; + } + } + + /** + * Unhooks a chunk of max. {@code chunkSize} references from pending chain and + * returns the head of the chunk; elements of the chunk can be reached using + * {@link #discovered} links; the last in chunk is linked to null. + * + * @param chunkSize max. number of references to unhook from the pending chain + * @param morePending if non null, it should be a boolean array with length 1 + * to hold the additional result - a flag indicating that + * there are more pending references waiting after a chunk + * of them has been returned. + * @return the head of the chunk of max. {@code chunkSize} pending references or + * null if there are none pending. + */ + private static Reference unhookPendingChunk(int chunkSize, boolean[] morePending) { + // assert Thread.holdsLock(lock); + Reference r; + if ((r = pending) != null) { + // skip over max. chunkSize references using 'discovered' link + Reference p = r; + Reference d = p.discovered; + for (int i = 0; + d != null && ++i < chunkSize; + p = d, d = p.discovered) {} + pending = d; + p.discovered = null; // unlink last in unhooked chunk from the rest + if (morePending != null) morePending[0] = (d != null); + } else { + if (morePending != null) morePending[0] = false; + } + return r; + } + + /** + * Takes a non-null chunk of unhooked pending references + * (obtained using {@link #unhookPendingChunk}) and handles + * them as following: + *
    + *
  • sun.misc.Cleaner(s) are executed immediately
  • + *
  • java.lang.ref.Cleaner(s) are submitted in chunks as ForkJoinTask(s)
  • + *
  • all other Reference(s) are enqueued in their respective queues
  • + *
+ * The references in chunk are linked using {@link #discovered} link. + * Last in chunk is linked to null. As they are handled, their discovered + * links are reset to null. + * + * @param chunk the head of a chunk of pending references + */ + static void handlePendingChunk(Reference chunk) { + // batch j.l.r.Cleaner(s) + Reference cleaners = null; + int cleanersCount = 0; + // batch runs of consecutive references having same queue + Reference referencesHead = null, referencesTail = null; + int referencesCount = 0; + ReferenceQueue referenceQueue = null; + // dispatch references to appropriate targets + for (Reference r = chunk, d = r.discovered; + r != null; + r = d, d = (r == null) ? null : r.discovered) { + // invariant established by GC when marking the reference as not active + // assert r.next == r; + if (r instanceof sun.misc.Cleaner) { // Fast path for sun.misc.Cleaners + // unlink from the rest in chunk + r.discovered = null; + ((sun.misc.Cleaner) r).clean(); + } else if (r instanceof Cleaner) { // Submit task(s) for j.l.r.Cleaner(s) + // link into the local cleaners list + r.discovered = cleaners; + cleaners = r; + if (++cleanersCount >= CLEANER_CHUNK_SIZE) { + // when chunk of finalizers is full, submit a task + new ReferenceHandling.CleanersHandler(cleaners).submit(); + cleaners = null; + cleanersCount = 0; + } + } else { // Enqueue all other references + // unlink from the rest in chunk + r.discovered = null; + ReferenceQueue q = r.queue; + if (q != ReferenceQueue.NULL && q.markEnqueued(r)) { // markEnqueued is atomic + if (referenceQueue == null || referenceQueue == q) { + // no queue or same queue -> hook onto the references[Head|Tail] chain + if (referencesHead == null) { + // assert referencesTail == null && referenceQueue == null && + // referencesCount == 0 && r.next == r; + referenceQueue = q; + referencesHead = referencesTail = r; + } else { + // assert referencesTail != null && referenceQueue == q && + // referencesCount > 0; + r.next = referencesHead; + referencesHead = r; + } + if (++referencesCount >= ENQUEUE_CHUNK_SIZE) { + // when a chunk of references is full, add them to queue + referenceQueue.addChunk(referencesHead, referencesTail); + referencesHead = referencesTail = null; + referenceQueue = null; + referencesCount = 0; + } + } else { + // when a different queue is encountered, + // add collected chunk to it's queue and start collecting + // new batch for new queue... + // assert referenceQueue != null && referenceQueue != q && + // referencesHead != null && referencesTail != null && + // referencesCount > 0 && r.next == r; + referenceQueue.addChunk(referencesHead, referencesTail); + referenceQueue = q; + referencesHead = referencesTail = r; + referencesCount = 1; } - // retry if waited - return waitForNotify; } + // else just drop it on the flor } - } catch (OutOfMemoryError x) { - // Give other threads CPU time so they hopefully drop some live references - // and GC reclaims some space. - // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above - // persistently throws OOME for some time... - Thread.yield(); - // retry - return true; - } catch (InterruptedException x) { - // retry - return true; - } - - // Fast path for cleaners - if (c != null) { - c.clean(); - return true; - } - - ReferenceQueue q = r.queue; - if (q != ReferenceQueue.NULL) q.enqueue(r); - return true; + } + // any j.l.r.Cleaner(s) left? + if (cleaners != null) { + new ReferenceHandling.CleanersHandler(cleaners).submit(); + cleaners = null; + cleanersCount = 0; + } + // any references left to enqueue? + if (referenceQueue != null) { + // assert referencesHead != null && referencesTail != null && referencesCount > 0; + referenceQueue.addChunk(referencesHead, referencesTail); + referencesHead = referencesTail = null; + referenceQueue = null; + referencesCount = 0; + } } - 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(); - - // provide access in SharedSecrets - SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { - @Override - public boolean tryHandlePendingReference() { - return tryHandlePending(false); + /** + * Handles a non-null chunk of j.l.r.Cleaner(s) + * + * @param cleaners the head of a chunk of Reference(s) implementing j.l.r.Cleaner + * linked using {@link #discovered} link. Last in chunk is + * linked to null. + */ + static void handleCleaners(Reference cleaners) { + for (Reference c = cleaners, d = c.discovered; + c != null; + c = d, d = (c == null) ? null : c.discovered) { + // unlink it from the rest in chunk + c.discovered = null; + // Handle it + try { + ((Cleaner) c).clean(); + } catch (Throwable t) { + // ignore } - }); + } } /* -- Referent accessor and setters -- */ @@ -306,7 +452,130 @@ Reference(T referent, ReferenceQueue queue) { this.referent = referent; + if (this instanceof Cleaner && queue != null) { + throw new IllegalArgumentException( + "Reference implementing Cleaner can't be registered with a reference queue"); + } this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } + // Methods that enable selected Reference subclasses to be elements of a DLList + + /** + * Some Cleaner's (such as Finalizer) wish to be registered in a doubly-linked + * list so that they are kept alive until discovered by VM, processed by + * reference handling threads and then unlinked. There are several lists to + * distribute them randomly into to reduce contention among concurrent threads + * trying to link/unlink them. + */ + static final DLList[] uncleanedLists; + + /** + * Register this reference into the associated doubly-linked list. + */ + final void link() { + int h = System.identityHashCode(this); + int i = (h >>> 1) & (uncleanedLists.length - 1); + uncleanedLists[i].link(this, (h & 1) == 0); + } + + /** + * De-register this reference from the associated doubly-linked list. + */ + final void unlink() { + int h = System.identityHashCode(this); + int i = (h >>> 1) & (uncleanedLists.length - 1); + uncleanedLists[i].unlink(this); + } + + boolean isDeletedDll() { + throw new UnsupportedOperationException(); + } + + Reference getPrevDll() { + throw new UnsupportedOperationException(); + } + + void lazySetPrevDll(Reference val) { + throw new UnsupportedOperationException(); + } + + boolean casPrevDll(Reference cmp, Reference val) { + throw new UnsupportedOperationException(); + } + + Reference getNextDll() { + throw new UnsupportedOperationException(); + } + + void lazySetNextDll(Reference val) { + throw new UnsupportedOperationException(); + } + + boolean casNextDll(Reference cmp, Reference val) { + throw new UnsupportedOperationException(); + } + + // Unsafe machinery + + @SuppressWarnings("unchecked") + T getReferentVolatile() { + return (T) UNSAFE.getObjectVolatile(this, referentOffset); + } + + boolean casReferent(T cmp, T val) { + return UNSAFE.compareAndSwapObject(this, referentOffset, cmp, val); + } + + void lazySetQueue(ReferenceQueue val) { + UNSAFE.putOrderedObject(this, queueOffset, val); + } + + boolean casQueue(ReferenceQueue cmp, ReferenceQueue val) { + return UNSAFE.compareAndSwapObject(this, queueOffset, cmp, val); + } + + private static final sun.misc.Unsafe UNSAFE; + private static final long referentOffset; + private static final long queueOffset; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class rc = Reference.class; + referentOffset = UNSAFE.objectFieldOffset(rc.getDeclaredField("referent")); + queueOffset = UNSAFE.objectFieldOffset(rc.getDeclaredField("queue")); + } catch (Exception e) { + throw new Error(e); + } + + // DLList(s) for selected uncleaned Cleaner(s) + int cpus = Runtime.getRuntime().availableProcessors(); + // smallest power of two equal or greater than 2 * # of CPUs + int lists = (cpus <= 1) ? 2 : Integer.highestOneBit(cpus - 1) << 2; + uncleanedLists = new DLList[lists]; + for (int i = 0; i < uncleanedLists.length; i++) { + uncleanedLists[i] = new DLList(); + } + + 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(); + + // provide access in SharedSecrets + SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { + @Override + public boolean tryHandlePendingReference() { + return tryHandlePending(); + } + }); + } } --- old/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-07-03 16:04:57.458713794 +0200 +++ new/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-07-03 16:04:57.388713308 +0200 @@ -46,51 +46,70 @@ } } - static ReferenceQueue NULL = new Null<>(); - static ReferenceQueue ENQUEUED = new Null<>(); + static final ReferenceQueue NULL = new Null<>(); + static final ReferenceQueue ENQUEUED = new Null<>(); - static private class Lock { }; - private Lock lock = new Lock(); - private volatile Reference head = null; - private long queueLength = 0; + static private class Lock { } + private final Lock lock = new Lock(); + private volatile int waiters; + + @SuppressWarnings("unused") + private volatile Reference head; // we assign using Unsafe CAS boolean enqueue(Reference r) { /* Called only by Reference class */ - synchronized (lock) { - // Check that since getting the lock this reference hasn't already been + if (markEnqueued(r)) { // markEnqueued is atomic + addChunk(r, r); + return true; + } else { + return false; + } + } + + boolean markEnqueued(Reference r) { + ReferenceQueue queue; + do { + // Check that this reference hasn't already been // enqueued (and even then removed) - ReferenceQueue queue = r.queue; + queue = r.queue; if ((queue == NULL) || (queue == ENQUEUED)) { return false; } - assert queue == this; - r.queue = ENQUEUED; - r.next = (head == null) ? r : head; - head = r; - queueLength++; - if (r instanceof FinalReference) { - sun.misc.VM.addFinalRefCount(1); + } while (!r.casQueue(queue, ENQUEUED)); + assert queue == this; + return true; + } + + @SuppressWarnings("unchecked") + void addChunk(Reference chunkHead, Reference chunkTail) { + Reference h; + do { + h = head; + chunkTail.next = (h == null) ? chunkTail : h; + } while (!casHead(h, (Reference) chunkHead)); + // notify waiters + if (waiters > 0) { + synchronized (lock) { + if (waiters > 0) { + lock.notifyAll(); + } } - lock.notifyAll(); - return true; } } - @SuppressWarnings("unchecked") - private Reference reallyPoll() { /* Must hold lock */ - Reference r = head; - if (r != null) { - head = (r.next == r) ? - null : - r.next; // Unchecked due to the next field having a raw type in Reference - r.queue = NULL; - r.next = r; - queueLength--; - if (r instanceof FinalReference) { - sun.misc.VM.addFinalRefCount(-1); + private Reference reallyPoll() { + Reference r; + while ((r = head) != null) { + @SuppressWarnings("unchecked") // due to cast to raw type + Reference nh = (r.next == r) + ? null : (Reference) r.next; + if (casHead(r, nh)) { + r.lazySetQueue(NULL); + UNSAFE.storeFence(); + r.next = r; + break; } - return r; } - return null; + return r; } /** @@ -102,11 +121,7 @@ * otherwise null */ public Reference poll() { - if (head == null) - return null; - synchronized (lock) { - return reallyPoll(); - } + return reallyPoll(); } /** @@ -135,20 +150,31 @@ if (timeout < 0) { throw new IllegalArgumentException("Negative timeout value"); } + Reference r = reallyPoll(); + if (r != null) return r; + return reallyRemove(timeout); + } + + private Reference reallyRemove(long timeout) throws InterruptedException { + long deadline = (timeout == 0) + ? 0 : System.nanoTime() + timeout * 1000_000L; + synchronized (lock) { - Reference 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; + ++waiters; + try { + for (; ; ) { + Reference r = reallyPoll(); + if (r != null) return r; + if (timeout == 0) { + lock.wait(0); + } else { + long timeoutNanos = deadline - System.nanoTime(); + if (timeoutNanos <= 0) return null; + lock.wait(timeoutNanos / 1000_000L, (int) (timeoutNanos % 1000_000L)); + } } + } finally { + --waiters; } } } @@ -164,4 +190,22 @@ return remove(0); } + // Unsafe machinery + + private boolean casHead(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); + } + + private static final sun.misc.Unsafe UNSAFE; + private static final long headOffset; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class rqc = ReferenceQueue.class; + headOffset = UNSAFE.objectFieldOffset(rqc.getDeclaredField("head")); + } catch (Exception e) { + throw new Error(e); + } + } } --- old/src/java.base/share/classes/sun/misc/Cleaner.java 2015-07-03 16:04:57.665715229 +0200 +++ new/src/java.base/share/classes/sun/misc/Cleaner.java 2015-07-03 16:04:57.596714751 +0200 @@ -60,12 +60,6 @@ extends PhantomReference { - // Dummy reference queue, needed because the PhantomReference constructor - // insists that we pass a queue. Nothing will ever be placed on this queue - // since the reference handler invokes cleaners explicitly. - // - private static final ReferenceQueue dummyQueue = new ReferenceQueue<>(); - // Doubly-linked list of live cleaners, which prevents the cleaners // themselves from being GC'd before their referents // @@ -112,7 +106,7 @@ private final Runnable thunk; private Cleaner(Object referent, Runnable thunk) { - super(referent, dummyQueue); + super(referent, null); this.thunk = thunk; } --- /dev/null 2015-07-01 10:11:09.377158340 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Cleaner.java 2015-07-03 16:04:57.800716165 +0200 @@ -0,0 +1,53 @@ +package java.lang.ref; + +/** + * {@link Reference}(s) implementing this interface are automatically + * {@link #clean() cleaned} by reference handling thread(s) after their referents + * become appropriately (soft, weak or phantom) reachable. + *

+ * Such references can not be associated with a {@link ReferenceQueue}. Attempts + * to register a {@link SoftReference#SoftReference(Object, ReferenceQueue) soft}, + * {@link WeakReference#WeakReference(Object, ReferenceQueue) weak} or + * {@link PhantomReference#PhantomReference(Object, ReferenceQueue) phantom} + * reference implementing this interface with a non-null {@code ReferenceQueue} + * throw {@link IllegalArgumentException}. + * + * @since 1.9 + */ +public interface Cleaner { + + /** + * Invoked by one of the reference handling thread(s) some time after the + * referent of a {@link Reference} implementing this interface becomes + * appropriately (soft, weak or phantom) reachable and after the Reference + * is cleared by the garbage collector.

+ * Implementations should put the clean-up code into this method. + */ + void clean(); + + /** + * Always returns false. References implementing {@link Cleaner} + * interface are never associated with a {@link ReferenceQueue}. + * + * @return false; + */ + boolean isEnqueued(); + + /** + * Does nothing and returns false. References implementing {@link Cleaner} + * interface are never associated with a {@link ReferenceQueue}. + * + * @return false + */ + boolean enqueue(); + + /** + * Clears this Cleaner reference object. Invoking this method will not cause + * this object to be {@link #clean() cleaned}. It may prevent this object + * from being cleaned in the future if it has not been discovered as a pending + * reference by GC yet. + *

This method is invoked only by Java code; when the garbage collector + * clears references it does so directly, without invoking this method. + */ + void clear(); +} --- /dev/null 2015-07-01 10:11:09.377158340 +0200 +++ new/src/java.base/share/classes/java/lang/ref/DLList.java 2015-07-03 16:04:58.043717850 +0200 @@ -0,0 +1,557 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from members of + * JCP JSR-166 Expert Group and released to the public domain, as explained + * at http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.lang.ref; + +/** + * A concurrent doubly-linked list of {@link java.lang.ref.Reference} nodes + * modeled by {@link java.util.concurrent.ConcurrentLinkedDeque}. + */ +final class DLList { + + /** + * A node from which the first node on list (that is, the unique node p + * with p.getPrevDll() == null && p.getNextDll() != p) can be reached in O(1) time. + * Invariants: + * - the first node is always O(1) reachable from head via prev links + * - all live nodes are reachable from the first node via succ() + * - head != null + * - (tmp = head).getNextDll() != tmp || tmp != head + * - head is never gc-unlinked (but may be unlinked) + * Non-invariants: + * - head.isDeleted() may or may not be true + * - head may not be reachable from the first or last node, or from tail + */ + private volatile Reference head; + + /** + * A node from which the last node on list (that is, the unique node p + * with p.getNextDll() == null && p.getPrevDll() != p) can be reached in O(1) time. + * Invariants: + * - the last node is always O(1) reachable from tail via next links + * - all live nodes are reachable from the last node via pred() + * - tail != null + * - tail is never gc-unlinked (but may be unlinked) + * Non-invariants: + * - tail.isDeletedDll() may or may not be true + * - tail may not be reachable from the first or last node, or from head + */ + private volatile Reference tail; + + private static final Reference PREV_TERMINATOR, NEXT_TERMINATOR; + + /** + * Links newReference as first or last element, depending on + * specified boolean flag. + */ + void link(Reference newReference, boolean first) { + if (first) { + linkFirst(newReference); + } else { + linkLast(newReference); + } + } + + /** + * Links newReference as first element. + */ + private void linkFirst(Reference newReference) { + + restartFromHead: + for (;;) + for (Reference h = head, p = h, q;;) { + if ((q = p.getPrevDll()) != null && + (q = (p = q).getPrevDll()) != null) + // Check for head updates every other hop. + // If p == q, we are sure to follow head instead. + p = (h != (h = head)) ? h : q; + else if (p.getNextDll() == p) // PREV_TERMINATOR + continue restartFromHead; + else { + // p is first node + newReference.lazySetNextDll(p); // CAS piggyback + if (p.casPrevDll(null, newReference)) { + // Successful CAS is the linearization point + // for e to become an element of this deque, + // and for newNode to become "live". + if (p != h) // hop two nodes at a time + casHead(h, newReference); // Failure is OK. + return; + } + // Lost CAS race to another thread; re-read prev + } + } + } + + /** + * Links newReference as last element. + */ + private void linkLast(Reference newReference) { + + restartFromTail: + for (;;) + for (Reference t = tail, p = t, q;;) { + if ((q = p.getNextDll()) != null && + (q = (p = q).getNextDll()) != null) + // Check for tail updates every other hop. + // If p == q, we are sure to follow tail instead. + p = (t != (t = tail)) ? t : q; + else if (p.getPrevDll() == p) // NEXT_TERMINATOR + continue restartFromTail; + else { + // p is last node + newReference.lazySetPrevDll(p); // CAS piggyback + if (p.casNextDll(null, newReference)) { + // Successful CAS is the linearization point + // for e to become an element of this deque, + // and for newNode to become "live". + if (p != t) // hop two nodes at a time + casTail(t, newReference); // Failure is OK. + return; + } + // Lost CAS race to another thread; re-read next + } + } + } + + private static final int HOPS = 2; + + /** + * Unlinks non-null node x. + */ + void unlink(Reference x) { + // assert x != null; + // assert x.isDeletedDll(); + // assert x != PREV_TERMINATOR; + // assert x != NEXT_TERMINATOR; + + final Reference prev = x.getPrevDll(); + final Reference next = x.getNextDll(); + if (prev == null) { + unlinkFirst(x, next); + } else if (next == null) { + unlinkLast(x, prev); + } else { + // Unlink interior node. + // + // This is the common case, since a series of polls at the + // same end will be "interior" removes, except perhaps for + // the first one, since end nodes cannot be unlinked. + // + // At any time, all active nodes are mutually reachable by + // following a sequence of either next or prev pointers. + // + // Our strategy is to find the unique active predecessor + // and successor of x. Try to fix up their links so that + // they point to each other, leaving x unreachable from + // active nodes. If successful, and if x has no live + // predecessor/successor, we additionally try to gc-unlink, + // leaving active nodes unreachable from x, by rechecking + // that the status of predecessor and successor are + // unchanged and ensuring that x is not reachable from + // tail/head, before setting x's prev/next links to their + // logical approximate replacements, self/TERMINATOR. + Reference activePred, activeSucc; + boolean isFirst, isLast; + int hops = 1; + + // Find active predecessor + for (Reference p = prev; ; ++hops) { + if (!p.isDeletedDll()) { + activePred = p; + isFirst = false; + break; + } + Reference q = p.getPrevDll(); + if (q == null) { + if (p.getNextDll() == p) + return; + activePred = p; + isFirst = true; + break; + } + else if (p == q) + return; + else + p = q; + } + + // Find active successor + for (Reference p = next; ; ++hops) { + if (!p.isDeletedDll()) { + activeSucc = p; + isLast = false; + break; + } + Reference q = p.getNextDll(); + if (q == null) { + if (p.getPrevDll() == p) + return; + activeSucc = p; + isLast = true; + break; + } + else if (p == q) + return; + else + p = q; + } + + // TODO: better HOP heuristics + if (hops < HOPS + // always squeeze out interior deleted nodes + && (isFirst | isLast)) + return; + + // Squeeze out deleted nodes between activePred and + // activeSucc, including x. + skipDeletedSuccessors(activePred); + skipDeletedPredecessors(activeSucc); + + // Try to gc-unlink, if possible + if ((isFirst | isLast) && + + // Recheck expected state of predecessor and successor + (activePred.getNextDll() == activeSucc) && + (activeSucc.getPrevDll() == activePred) && + (isFirst ? activePred.getPrevDll() == null : !activePred.isDeletedDll()) && + (isLast ? activeSucc.getNextDll() == null : !activeSucc.isDeletedDll())) { + + updateHead(); // Ensure x is not reachable from head + updateTail(); // Ensure x is not reachable from tail + + // Finally, actually gc-unlink + x.lazySetPrevDll(isFirst ? PREV_TERMINATOR : x); + x.lazySetNextDll(isLast ? NEXT_TERMINATOR : x); + } + } + } + + /** + * Unlinks non-null first node. + */ + private void unlinkFirst(Reference first, Reference next) { + // assert first != null; + // assert next != null; + // assert first.isDeletedDll(); + for (Reference o = null, p = next, q;;) { + if (!p.isDeletedDll() || (q = p.getNextDll()) == null) { + if (o != null && p.getPrevDll() != p && first.casNextDll(next, p)) { + skipDeletedPredecessors(p); + if (first.getPrevDll() == null && + (p.getNextDll() == null || !p.isDeletedDll()) && + p.getPrevDll() == first) { + + updateHead(); // Ensure o is not reachable from head + updateTail(); // Ensure o is not reachable from tail + + // Finally, actually gc-unlink + o.lazySetNextDll(o); + o.lazySetPrevDll(PREV_TERMINATOR); + } + } + return; + } + else if (p == q) + return; + else { + o = p; + p = q; + } + } + } + + /** + * Unlinks non-null last node. + */ + private void unlinkLast(Reference last, Reference prev) { + // assert last != null; + // assert prev != null; + // assert last.isDeletedDll(); + for (Reference o = null, p = prev, q;;) { + if (!p.isDeletedDll() || (q = p.getPrevDll()) == null) { + if (o != null && p.getNextDll() != p && last.casPrevDll(prev, p)) { + skipDeletedSuccessors(p); + if (last.getNextDll() == null && + (p.getPrevDll() == null || !p.isDeletedDll()) && + p.getNextDll() == last) { + + updateHead(); // Ensure o is not reachable from head + updateTail(); // Ensure o is not reachable from tail + + // Finally, actually gc-unlink + o.lazySetPrevDll(o); + o.lazySetNextDll(NEXT_TERMINATOR); + } + } + return; + } + else if (p == q) + return; + else { + o = p; + p = q; + } + } + } + + /** + * Guarantees that any node which was unlinked before a call to + * this method will be unreachable from head after it returns. + * Does not guarantee to eliminate slack, only that head will + * point to a node that was active while this method was running. + */ + private void updateHead() { + // Either head already points to an active node, or we keep + // trying to cas it to the first node until it does. + Reference h, p, q; + restartFromHead: + while ((h = head).isDeletedDll() && (p = h.getPrevDll()) != null) { + for (;;) { + if ((q = p.getPrevDll()) == null || + (q = (p = q).getPrevDll()) == null) { + // It is possible that p is PREV_TERMINATOR, + // but if so, the CAS is guaranteed to fail. + if (casHead(h, p)) + return; + else + continue restartFromHead; + } + else if (h != head) + continue restartFromHead; + else + p = q; + } + } + } + + /** + * Guarantees that any node which was unlinked before a call to + * this method will be unreachable from tail after it returns. + * Does not guarantee to eliminate slack, only that tail will + * point to a node that was active while this method was running. + */ + private void updateTail() { + // Either tail already points to an active node, or we keep + // trying to cas it to the last node until it does. + Reference t, p, q; + restartFromTail: + while ((t = tail).isDeletedDll() && (p = t.getNextDll()) != null) { + for (;;) { + if ((q = p.getNextDll()) == null || + (q = (p = q).getNextDll()) == null) { + // It is possible that p is NEXT_TERMINATOR, + // but if so, the CAS is guaranteed to fail. + if (casTail(t, p)) + return; + else + continue restartFromTail; + } + else if (t != tail) + continue restartFromTail; + else + p = q; + } + } + } + + private void skipDeletedPredecessors(Reference x) { + whileActive: + do { + Reference prev = x.getPrevDll(); + // assert prev != null; + // assert x != NEXT_TERMINATOR; + // assert x != PREV_TERMINATOR; + Reference p = prev; + findActive: + for (;;) { + if (!p.isDeletedDll()) + break findActive; + Reference q = p.getPrevDll(); + if (q == null) { + if (p.getNextDll() == p) + continue whileActive; + break findActive; + } + else if (p == q) + continue whileActive; + else + p = q; + } + + // found active CAS target + if (prev == p || x.casPrevDll(prev, p)) + return; + + } while (!x.isDeletedDll() || x.getNextDll() == null); + } + + private void skipDeletedSuccessors(Reference x) { + whileActive: + do { + Reference next = x.getNextDll(); + // assert next != null; + // assert x != NEXT_TERMINATOR; + // assert x != PREV_TERMINATOR; + Reference p = next; + findActive: + for (;;) { + if (!p.isDeletedDll()) + break findActive; + Reference q = p.getNextDll(); + if (q == null) { + if (p.getPrevDll() == p) + continue whileActive; + break findActive; + } + else if (p == q) + continue whileActive; + else + p = q; + } + + // found active CAS target + if (next == p || x.casNextDll(next, p)) + return; + + } while (!x.isDeletedDll() || x.getPrevDll() == null); + } + + /** + * Returns the successor of p, or the first node if p.getNextDll() has been + * linked to self, which will only be true if traversing with a + * stale pointer that is now off the list. + */ + Reference succ(Reference p) { + // TODO: should we skip deleted nodes here? + Reference q = p.getNextDll(); + return (p == q) ? first() : q; + } + + /** + * Returns the predecessor of p, or the last node if p.getPrevDll() has been + * linked to self, which will only be true if traversing with a + * stale pointer that is now off the list. + */ + Reference pred(Reference p) { + Reference q = p.getPrevDll(); + return (p == q) ? last() : q; + } + + /** + * Returns the first node, the unique node p for which: + * p.getPrevDll() == null && p.getNextDll() != p + * The returned node may or may not be logically deleted. + * Guarantees that head is set to the returned node. + */ + Reference first() { + restartFromHead: + for (;;) + for (Reference h = head, p = h, q;;) { + if ((q = p.getPrevDll()) != null && + (q = (p = q).getPrevDll()) != null) + // Check for head updates every other hop. + // If p == q, we are sure to follow head instead. + p = (h != (h = head)) ? h : q; + else if (p == h + // It is possible that p is PREV_TERMINATOR, + // but if so, the CAS is guaranteed to fail. + || casHead(h, p)) + return p; + else + continue restartFromHead; + } + } + + /** + * Returns the last node, the unique node p for which: + * p.getNextDll() == null && p.getPrevDll() != p + * The returned node may or may not be logically deleted. + * Guarantees that tail is set to the returned node. + */ + Reference last() { + restartFromTail: + for (;;) + for (Reference t = tail, p = t, q;;) { + if ((q = p.getNextDll()) != null && + (q = (p = q).getNextDll()) != null) + // Check for tail updates every other hop. + // If p == q, we are sure to follow tail instead. + p = (t != (t = tail)) ? t : q; + else if (p == t + // It is possible that p is NEXT_TERMINATOR, + // but if so, the CAS is guaranteed to fail. + || casTail(t, p)) + return p; + else + continue restartFromTail; + } + } + + /** + * Constructs an empty list. + */ + DLList() { + head = tail = new Finalizer<>(false, false); + } + + // Unsafe mechanics + + private boolean casHead(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, HEAD, cmp, val); + } + + private boolean casTail(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, TAIL, cmp, val); + } + + private static final sun.misc.Unsafe UNSAFE; + private static final long HEAD; + private static final long TAIL; + static { + PREV_TERMINATOR = new Finalizer<>(false, true); + NEXT_TERMINATOR = new Finalizer<>(true, false); + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class flc = DLList.class; + HEAD = UNSAFE.objectFieldOffset + (flc.getDeclaredField("head")); + TAIL = UNSAFE.objectFieldOffset + (flc.getDeclaredField("tail")); + } catch (Exception e) { + throw new Error(e); + } + } +} --- /dev/null 2015-07-01 10:11:09.377158340 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Finalizator.java 2015-07-03 16:04:58.284719521 +0200 @@ -0,0 +1,165 @@ +package java.lang.ref; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + *

Finalizator provides an alternative form of finalization which can be more + * efficiently combined with (almost) regular manual cleanup where it serves as + * a last-resort cleanup mechanism when manual cleanup is not performed. + *

+ * Instead of overriding the Object's {@link #finalize()} method, a class + * arranges in it's constructor(s) to {@link #create(Object, Consumer) create} a + * {@code Finalizator} object which is used by GC to track the reachability of + * given {@code finalizee} and invoke given {@code thunk} with it when such + * {@code finalizee} becomes unreachable. + *

+ * {@code Finalizator} can be {@link #clean() invoked} manually by user code that + * decides to perform cleanup even before GC invokes it. The 1st invocation of + * {@link #clean()} method performs the cleanup by invoking the {@code thunk}, + * subsequent invocations are ignored. + *

+ * After the cleanup is performed, the + * {@code Finalizator} is {@link #clear() cleared} which breaks the links between: + *

    + *
  • finalizator and finalizee,
  • + *
  • finalizator and thunk,
  • + *
  • platform and finalizator
  • + *
+ * In the absence of other links, finalizator, finalizee and thunk become + * unreachable and eligible for GC. + *

+ * Here's an example of a classic finalizable class: + *

{@code
+ *      public class Classic {
+ *          @Override
+ *          protected void finalize() {
+ *              // clean-up actions invoked at most once...
+ *          }
+ *      }
+ * }
+ *

+ * And this is an alternative using {@code Finalizator}, combining finalization + * with manual cleanup: + *

{@code
+ *      public class Alternative {
+ *          private final Finalizator finalizator =
+ *              Finalizator.create(this, Alternative::cleanup);
+ *
+ *          void cleanup() {
+ *              // clean-up actions invoked at most once...
+ *          }
+ *
+ *          // manually triggered cleanup
+ *          public void close() {
+ *              finalizator.run();
+ *          }
+ *      }
+ * }
+ * + * @param the type of finalizee tracked by Finalizator + * @since 1.9 + */ +public final class Finalizator extends Finalizer implements Cleaner { + + private Consumer thunk; + + /** + * Creates and returns an instance of Finalizator used by GC to track given + * {@code finalizee} and invoke given {@code thunk} with it when the + * finalizee becomes unreachable. + * + * @param finalizee the instance to track it's reachability + * @param thunk a {@link Consumer} which is invoked and passed the + * {@code finalizee} when it becomes unreachable. + * @param the type of {@code finalizee} + * @return a Finalizator used by GC to track given + * {@code finalizee} and invoke given {@code thunk} with it when the + * finalizee becomes unreachable + * @throws NullPointerException if either {@code finalizee} of {@code thunk} + * are null + */ + public static Finalizator create(T finalizee, Consumer thunk) { + Objects.requireNonNull(finalizee); + Objects.requireNonNull(thunk); + Finalizator finalizator = new Finalizator<>(finalizee, thunk); + finalizator.link(); + return finalizator; + } + + private Finalizator(T finalizee, Consumer thunk) { + super(finalizee); + this.thunk = thunk; + } + + /** + * Invoked by GC when the tracked finalizee becomes unreachable or + * by user code at any time. + * It invokes the finalizator's thunk with the finalizee if this + * Finalizator has not been {@link #clear() cleared} before that. If invoked + * multiple times, only the 1st invocation results in the invocation + * of the thunk. The finalizator releases the reference to the thunk and + * finalizee upon 1st invocation of this method regardless of whether + * it was performed by GC or by user code. + */ + @Override + public void clean() { + super.clean(); + } + + /** + * Invoke the {@link #thunk} passing the {@code finalizee} to it. + */ + @Override + void invokeFinalizee(T finalizee) throws Throwable { + Consumer thunk = this.thunk; + this.thunk = null; + thunk.accept(finalizee); + finalizee = null; + } + + /** + * Returns {@code null} to prevent unwanted retention of the tracked + * {@code finalizee}. + * + * @return {@code null} + */ + @Override + public T get() { + return null; + } + + /** + * Finalizator is not registered with a reference queue when created, + * so this method always returns {@code false}. + * + * @return {@code false} + */ + @Override + public boolean isEnqueued() { + return false; + } + + /** + * Finalizator is not registered with a reference queue when created, + * so this method does nothing and always returns {@code false}. + * + * @return {@code false} + */ + @Override + public boolean enqueue() { + return false; + } + + /** + * Clears this Finalizator and releases it's tracked {@code finalizee} and + * it's {@code thunk} if they have not been released yet. + * Invoking this method does not invoke the Finalizator's {@code thunk}. + * It prevents from {@link #clean() running} the {@code thunk} in the future + * if it has not been run yet. + */ + @Override + public void clear() { + super.clear(); + } +} --- /dev/null 2015-07-01 10:11:09.377158340 +0200 +++ new/src/java.base/share/classes/java/lang/ref/PhantomCleaner.java 2015-07-03 16:04:58.521721164 +0200 @@ -0,0 +1,151 @@ +package java.lang.ref; + +import java.util.Objects; + +/** + * A {@link PhantomReference} based {@link Cleaner}. + *

+ * PhantomCleaner(s) are a lightweight and more robust alternative to finalization. + * They are lightweight because they are not created by the VM and thus do not + * require a JNI upcall to be created. They are more robust because they are + * phantom references, the weakest type of reference object, thereby avoiding the + * nasty ordering problems inherent to finalization. + *

+ * Some time after the GC detects that PhantomCleaner's referent has + * become phantom-reachable, one of the finalizing thread(s) will + * {@link #clean() invoke} the cleaner. + * PhantomCleaner(s) may also be {@link #clean() invoked} directly. The 1st + * invocation will {@link #clear()} the PhantomCleaner and invoke the + * {@link #doClean() clean-up action}. Subsequent invocations are ignored. + * + * @since 1.9 + */ +public abstract class PhantomCleaner extends PhantomReference implements Cleaner { + + /** + * Creates and returns a PhantomCleaner implementation that takes a + * {@code Runnable thunk} to be run as a clean-up action. + * Some time after the {@code referent} becomes phantom-reachable, one of + * the finalizing threads runs the {@code thunk}. + * + * @param referent the referent object to be tracked + * @param thunk The cleanup code to be run when the cleaner is invoked. The + * cleanup code is run from one of the finalizing thread(s) + * that are also used to run other cleaners and finalization or + * from user code that invokes the cleaner directly. + * @return The new PhantomCleaner + * @throws NullPointerException if {@code referent} or {@code thunk} is null. + */ + public static PhantomCleaner create(Object referent, Runnable thunk) { + Objects.requireNonNull(thunk); + return new PhantomCleaner(referent) { + @Override + protected void doClean() { + thunk.run(); + } + }; + } + + /** + * A constructor taking a referent to track. + * + * @param referent the referent to track + * @throws NullPointerException if {@code referent} is null + */ + public PhantomCleaner(Object referent) { + super(Objects.requireNonNull(referent), null); + link(); + } + + /** + * Invoked at most once after the referent of this + * {@code PhantomCleaner} is found phantom-reachable or when the user + * explicitly invokes {@link #clean()}. Subclasses should implement this + * method and place clean-up actions into it. + */ + protected abstract void doClean(); + + /** + * Runs this PhantomCleaner, if it has not been run before. This method may be + * invoked by one of the finalizing thread(s) or manually by a client + * thread. Only the 1st invocation will {@link #clear()} this PhantomCleaner + * and invoke {@link #doClean() cleanup-code}. Any unchecked + * exception thrown from the {@link #doClean() cleanup-code} will be propagated. + * When such exception happens as a result of running this method automatically + * by one of the finalizing thread(s), it is ignored. + */ + @Override + public final void clean() { + if (delete()) { + super.clear(); + unlink(); + doClean(); + } + } + + @Override + public final void clear() { + if (delete()) { + super.clear(); + unlink(); + } + } + + // Methods and state that enable PhantomCleaner to be an element of DLList + + @SuppressWarnings("unused") // assigned through Unsafe + private volatile Reference prevDll, nextDll; + @SuppressWarnings("unused") // assigned through Unsafe + private volatile int deletedDll; + + private boolean delete() { + return deletedDll == 0 && UNSAFE.compareAndSwapInt(this, DELETED_DLL, 0, 1); + } + + boolean isDeletedDll() { + return deletedDll == 1; + } + + Reference getPrevDll() { + return prevDll; + } + + void lazySetPrevDll(Reference val) { + UNSAFE.putOrderedObject(this, PREV_DLL, val); + } + + boolean casPrevDll(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, PREV_DLL, cmp, val); + } + + Reference getNextDll() { + return nextDll; + } + + void lazySetNextDll(Reference val) { + UNSAFE.putOrderedObject(this, NEXT_DLL, val); + } + + boolean casNextDll(Reference cmp, Reference val) { + return UNSAFE.compareAndSwapObject(this, NEXT_DLL, cmp, val); + } + + // Unsafe machinery + + private static final sun.misc.Unsafe UNSAFE; + private static final long PREV_DLL; + private static final long NEXT_DLL; + private static final long DELETED_DLL; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class pcc = PhantomCleaner.class; + PREV_DLL = UNSAFE.objectFieldOffset(pcc.getDeclaredField("prevDll")); + NEXT_DLL = UNSAFE.objectFieldOffset(pcc.getDeclaredField("nextDll")); + DELETED_DLL = UNSAFE.objectFieldOffset(pcc.getDeclaredField("deletedDll")); + } catch (Exception e) { + throw new Error(e); + } + } +} --- /dev/null 2015-07-01 10:11:09.377158340 +0200 +++ new/src/java.base/share/classes/java/lang/ref/ReferenceHandling.java 2015-07-03 16:04:58.759722814 +0200 @@ -0,0 +1,114 @@ +package java.lang.ref; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeUnit; + +/** + * A holder for a ForkJoinPool and tasks for handling pending and + * Cleaner references. + */ +final class ReferenceHandling { + + private static final ForkJoinPool pool = createPool(); + + /** + * Called from {@link Finalizer#runFinalization()} as part of forked secondary + * finalizer thread to run all pending AutoRunnable(s). We just help the ForkJoinPool + * by lending current thread while waiting for pool to quiesce. + */ + static void runFinalization() { + do { + } while (!pool.awaitQuiescence(5, TimeUnit.SECONDS)); + } + + /** + * Creates new ForkJoinPool for handling the references. + */ + private static ForkJoinPool createPool() { + String refHandlingThreadsString = + System.getProperty("java.lang.ref.referenceHandlingThreads", "1"); + int referenceHandlingThreads; + try { + referenceHandlingThreads = Math.min( + Runtime.getRuntime().availableProcessors(), + Math.max(1, Integer.parseInt(refHandlingThreadsString)) + ); + } catch (NumberFormatException e) { + referenceHandlingThreads = 1; + } + return new ForkJoinPool( + referenceHandlingThreads, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, + true + ); + } + + /** + * A task that handles one chunk of references. A chunk + * is linked using 'discovered' links. Last in chunk is linked to 'null'. + */ + static final class PendingChunkHandler extends RecursiveAction { + private static final long serialVersionUID = 1L; + + private Reference chunk; + + PendingChunkHandler(Reference chunk) { + this.chunk = chunk; + } + + @Override + protected void compute() { + Reference r = this.chunk; + if (r != null) { + this.chunk = null; + Reference.handlePendingChunk(r); + } + } + + void submit() { + if (Thread.currentThread() instanceof ForkJoinWorkerThread) { + // internal submission + fork(); + } else { + // external submission + pool.submit(this); + } + } + } + + /** + * A task for handling a chunk of j.l.r.Cleaner Reference(s). A chunk + * is linked using 'discovered' links. Last in chunk is linked to null. + */ + static final class CleanersHandler extends RecursiveAction { + private static final long serialVersionUID = 1L; + + private Reference cleaners; + + CleanersHandler(Reference cleaners) { + this.cleaners = cleaners; + } + + @Override + protected void compute() { + Reference c = cleaners; + if (c != null) { + cleaners = null; + Reference.handleCleaners(c); + } + } + + void submit() { + if (Thread.currentThread() instanceof ForkJoinWorkerThread) { + // internal submission + fork(); + } else { + // external submission + pool.submit(this); + } + } + } +}