--- old/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-05-28 17:55:24.581602084 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Finalizer.java 2015-05-28 17:55:24.518601945 +0200 @@ -27,83 +27,67 @@ import java.security.PrivilegedAction; import java.security.AccessController; -import sun.misc.JavaLangAccess; +import java.util.concurrent.ThreadLocalRandom; 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; +/* Package-private; must be in same package as the Reference class */ +final class Finalizer extends FinalReference { + /** + * Finalizers are registered in a doubly-linked list so that they are kept + * alive until discovered by VM, processed by ReferenceHandling pool and then + * unlinked. There are several lists to distribute Finalizers randomly into + * to reduce contention among concurrent threads trying to link/unlink them. + */ + private static final FinalizerList[] unfinalized; + static { + 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; + unfinalized = new FinalizerList[lists]; + for (int i = 0; i < lists; i++) { + unfinalized[i] = new FinalizerList(); } } - 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; - } + volatile Finalizer prev; + volatile Finalizer next; + private final int listIndex; + + private Finalizer(Object finalizee, int listIndex) { + super(finalizee, ReferenceQueue.NULL); + this.listIndex = listIndex; } - private Finalizer(Object finalizee) { - super(finalizee, queue); - add(); + /** A constructor used for special Finalizer instances in FinalizerList */ + Finalizer() { + super(null, ReferenceQueue.NULL); + listIndex = -1; // never registered in any list } /* Invoked by VM */ static void register(Object finalizee) { - new Finalizer(finalizee); + int rnd = nextSecondarySeed(); + int index = (rnd >>> 1) & (unfinalized.length - 1); + unfinalized[index].link(new Finalizer(finalizee, index), (rnd & 1) == 0); } - private void runFinalizer(JavaLangAccess jla) { - synchronized (this) { - if (hasBeenFinalized()) return; - remove(); + void runFinalizer() { + Object finalizee = delete(); + if (finalizee == null) { + return; } + unfinalized[listIndex].unlink(this); try { - Object finalizee = this.get(); - if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { - jla.invokeFinalize(finalizee); + if (!(finalizee instanceof java.lang.Enum)) { + SharedSecrets.getJavaLangAccess().invokeFinalize(finalizee); /* Clear stack slot containing this variable, to decrease the chances of false retention with a conservative GC */ finalizee = null; } } catch (Throwable x) { } - super.clear(); } /* Create a privileged secondary finalizer thread in the system thread @@ -150,13 +134,7 @@ // 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 +151,79 @@ // 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 (FinalizerList uflist : unfinalized) + for (Finalizer f = uflist.first(); f != null; f = uflist.succ(f)) { + f.runFinalizer(); }}}); } - 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 - } - } + // Unsafe mechanics + + /** + * Returns the pseudo-randomly initialized or updated secondary seed. + * Copied from ThreadLocalRandom due to package access restrictions. + */ + private static int nextSecondarySeed() { + int r; + Thread t = Thread.currentThread(); + if ((r = UNSAFE.getInt(t, threadLocalRandomSecondarySeedOffset)) != 0) { + r ^= r << 13; // xorshift + r ^= r >>> 17; + r ^= r << 5; } + else if ((r = ThreadLocalRandom.current().nextInt()) == 0) + r = 1; // avoid zero + UNSAFE.putInt(t, threadLocalRandomSecondarySeedOffset, r); + return r; } - 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(); + boolean isAlive() { + return getReferentVolatile() != null; } + boolean isDeleted() { + return getReferentVolatile() == null; + } + + private Object delete() { + Object referent = getReferentVolatile(); + return (referent != null) && casReferent(referent, null) + ? referent : null; + } + + void lazySetNext(Finalizer val) { + UNSAFE.putOrderedObject(this, nextOffset, val); + } + + boolean casNext(Finalizer cmp, Finalizer val) { + return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + } + + void lazySetPrev(Finalizer val) { + UNSAFE.putOrderedObject(this, prevOffset, val); + } + + boolean casPrev(Finalizer cmp, Finalizer val) { + return UNSAFE.compareAndSwapObject(this, prevOffset, cmp, val); + } + + private static final sun.misc.Unsafe UNSAFE; + private static final long prevOffset; + private static final long nextOffset; + private static final long threadLocalRandomSecondarySeedOffset; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class fc = Finalizer.class; + prevOffset = UNSAFE.objectFieldOffset(fc.getDeclaredField("prev")); + nextOffset = UNSAFE.objectFieldOffset(fc.getDeclaredField("next")); + Class tc = Thread.class; + threadLocalRandomSecondarySeedOffset = UNSAFE.objectFieldOffset + (tc.getDeclaredField("threadLocalRandomSecondarySeed")); + } catch (Exception e) { + throw new Error(e); + } + } } --- old/src/java.base/share/classes/java/lang/ref/Reference.java 2015-05-28 17:55:24.781602529 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2015-05-28 17:55:24.727602409 +0200 @@ -27,8 +27,8 @@ import sun.misc.Cleaner; import sun.misc.JavaLangRefAccess; -import sun.misc.ManagedLocalsThread; import sun.misc.SharedSecrets; +import sun.misc.VM; /** * Abstract base class for reference objects. This class defines the @@ -99,14 +99,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 +114,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 @@ -125,41 +124,16 @@ */ private static Reference pending = null; - /* High-priority thread to enqueue pending References + /* Max. number of references to unhook from pending chain in one chunk + * before releasing the lock, handling them and grabbing the + * lock again. */ - private static class ReferenceHandler extends ManagedLocalsThread { - - private static void ensureClassInitialized(Class clazz) { - try { - Class.forName(clazz.getName(), true, clazz.getClassLoader()); - } catch (ClassNotFoundException e) { - throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); - } - } - - static { - // pre-load and initialize InterruptedException and Cleaner classes - // 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(Cleaner.class); - } - - ReferenceHandler(ThreadGroup g, String name) { - super(g, name); - } - - public void run() { - while (true) { - tryHandlePending(true); - } - } - } + private static final int CHUNK_SIZE = 256; /** - * 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. * @@ -167,78 +141,103 @@ * {@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. + * @return {@code true} if there is be more {@link Reference}s pending. */ static boolean tryHandlePending(boolean waitForNotify) { - Reference r; - Cleaner c; + Reference r = pollPendingChunk(waitForNotify, null); + if (r == null) return false; + handlePendingChunk(r); + synchronized (lock) { + return pending != null; + } + } + + /** + * Polls a chunk of max. {@link #CHUNK_SIZE} references from pending chain and + * returns the head of the chunk; others can be reached using {@link #next} pointer; the + * last in chunk is linked to itself. + * + * @param waitForNotify if {@code true} and there were no pending + * {@link Reference}s, wait until notified from VM + * or interrupted; if {@code false}, return immediately + * when there are no pending {@link Reference}s. + * @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. {@link #CHUNK_SIZE} pending references or + * null if there are none pending. + */ + static Reference pollPendingChunk(boolean waitForNotify, boolean[] morePending) { + Reference r = null; 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; + if ((r = pending) != null) { + // pending state invariant established by VM: + // assert r.next == r; + // move a chunk of pending/discovered references to a + // temporary local r/next chain + Reference rd = r.discovered; + for (int i = 0; rd != null; rd = r.discovered) { + r.discovered = null; + if (++i >= CHUNK_SIZE) { + break; + } + rd.next = r; + r = rd; + } + pending = (Reference) rd; + if (morePending != null) morePending[0] = (rd != null); } else { + if (morePending != null) morePending[0] = false; // The waiting on the lock may cause an OutOfMemoryError // because it may try to allocate exception objects. if (waitForNotify) { lock.wait(); } - // retry if waited - return waitForNotify; } } } 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; + // ignore } - - // Fast path for cleaners - if (c != null) { - c.clean(); - return true; - } - - ReferenceQueue q = r.queue; - if (q != ReferenceQueue.NULL) q.enqueue(r); - return true; + return r; } - 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 pending references + * (obtained using {@link #pollPendingChunk(boolean, boolean[])}) and handles + * them as following: + *
    + *
  • Cleaner(s) are executed immediately
  • + *
  • Finalizer(s) are submitted as ForkJoinTask(s)
  • + *
  • all other Reference(s) are enqueued in their respected queues
  • + *
+ * @param r the head of a chunk of pending references + */ + static void handlePendingChunk(Reference r) { + // dispatch temporary local r/next chain to appropriate queues + for (Reference rn = r.next; ; r = rn, rn = r.next) { + // make 'r' appear to be just taken off the pending chain + r.next = r; + // Fast path for cleaners + if (r instanceof Cleaner) { + ((Cleaner) r).clean(); + } else if (r instanceof Finalizer) { + // submit task for finalizers + new ReferenceHandling.FinalizerHandler((Finalizer) r).submit(); + } else { + // Enqueue all other references + ReferenceQueue q = r.queue; + if (q != ReferenceQueue.NULL) q.enqueue((Reference)r); } - }); + if (rn == r) { // last in chain + break; + } + } } /* -- Referent accessor and setters -- */ @@ -309,4 +308,81 @@ this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } + private static void ensureClassInitialized(Class clazz) { + try { + Class.forName(clazz.getName(), true, clazz.getClassLoader()); + } catch (ClassNotFoundException e) { + throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); + } + } + + // 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); + } + + // pre-load and initialize InterruptedException and Cleaner classes + // so that we don't get into trouble later if there's + // memory shortage while loading/initializing them lazily. + ensureClassInitialized(InterruptedException.class); + ensureClassInitialized(Cleaner.class); + + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + for (ThreadGroup tgn = tg; + tgn != null; + tg = tgn, tgn = tg.getParent()); + + // must wait for VM to boot-up before starting ReferenceHandling + // as ForkJoinPool initialization accesses system properties + new Thread(tg, "ReferenceHandlingStarter") { + @Override + public void run() { + while (true) { + try { + VM.awaitBooted(); + break; + } catch (InterruptedException e) { + // ignore + } + } + ReferenceHandling.start(); + } + }.start(); + + // provide access in SharedSecrets + SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { + @Override + public boolean tryHandlePendingReference() { + return tryHandlePending(false); + } + }); + } } --- old/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-05-28 17:55:24.982602976 +0200 +++ new/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java 2015-05-28 17:55:24.927602853 +0200 @@ -46,51 +46,59 @@ } } - 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 + 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; + Reference h; + do { + h = head; + r.next = (h == null) ? r : h; + } while (!casHead(h, r)); + + if (waiters > 0) { + synchronized (lock) { + if (waiters > 0) { + lock.notifyAll(); + } } - lock.notifyAll(); - return true; } + 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; + if (r instanceof FinalReference) { + sun.misc.VM.addFinalRefCount(-1); + } + break; } - return r; } - return null; + return r; } /** @@ -102,11 +110,7 @@ * otherwise null */ public Reference poll() { - if (head == null) - return null; - synchronized (lock) { - return reallyPoll(); - } + return reallyPoll(); } /** @@ -135,20 +139,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; + int w = waiters; + waiters = w + 1; + 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 = w; } } } @@ -164,4 +179,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); + } + } } --- /dev/null 2015-04-10 13:19:24.023596515 +0200 +++ new/src/java.base/share/classes/java/lang/ref/FinalizerList.java 2015-05-28 17:55:25.127603298 +0200 @@ -0,0 +1,559 @@ +/* + * 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.Finalizer} nodes + * modeled by {@link java.util.concurrent.ConcurrentLinkedDeque}. + */ +final class FinalizerList { + + /** + * A node from which the first node on list (that is, the unique node p + * with p.prev == null && p.next != 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).next != tmp || tmp != head + * - head is never gc-unlinked (but may be unlinked) + * Non-invariants: + * - head.item may or may not be null + * - head may not be reachable from the first or last node, or from tail + */ + private volatile Finalizer head; + + /** + * A node from which the last node on list (that is, the unique node p + * with p.next == null && p.prev != 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.item may or may not be null + * - tail may not be reachable from the first or last node, or from head + */ + private volatile Finalizer tail; + + private static final Finalizer PREV_TERMINATOR, NEXT_TERMINATOR; + + /** + * Links newFinalizer as first or last element, depending on + * specified boolean flag. + */ + void link(Finalizer newFinalizer, boolean first) { + if (first) { + linkFirst(newFinalizer); + } else { + linkLast(newFinalizer); + } + } + + /** + * Links newFinalizer as first element. + */ + private void linkFirst(Finalizer newFinalizer) { + + restartFromHead: + for (;;) + for (Finalizer h = head, p = h, q;;) { + if ((q = p.prev) != null && + (q = (p = q).prev) != 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.next == p) // PREV_TERMINATOR + continue restartFromHead; + else { + // p is first node + newFinalizer.lazySetNext(p); // CAS piggyback + if (p.casPrev(null, newFinalizer)) { + // 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, newFinalizer); // Failure is OK. + return; + } + // Lost CAS race to another thread; re-read prev + } + } + } + + /** + * Links newNode as last element. + */ + private void linkLast(Finalizer newFinalizer) { + + restartFromTail: + for (;;) + for (Finalizer t = tail, p = t, q;;) { + if ((q = p.next) != null && + (q = (p = q).next) != 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.prev == p) // NEXT_TERMINATOR + continue restartFromTail; + else { + // p is last node + newFinalizer.lazySetPrev(p); // CAS piggyback + if (p.casNext(null, newFinalizer)) { + // 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, newFinalizer); // 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(Finalizer x) { + // assert x != null; + // assert x.item == null; + // assert x != PREV_TERMINATOR; + // assert x != NEXT_TERMINATOR; + + final Finalizer prev = x.prev; + final Finalizer next = x.next; + 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. + Finalizer activePred, activeSucc; + boolean isFirst, isLast; + int hops = 1; + + // Find active predecessor + for (Finalizer p = prev; ; ++hops) { + if (p.isAlive()) { + activePred = p; + isFirst = false; + break; + } + Finalizer q = p.prev; + if (q == null) { + if (p.next == p) + return; + activePred = p; + isFirst = true; + break; + } + else if (p == q) + return; + else + p = q; + } + + // Find active successor + for (Finalizer p = next; ; ++hops) { + if (p.isAlive()) { + activeSucc = p; + isLast = false; + break; + } + Finalizer q = p.next; + if (q == null) { + if (p.prev == 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.next == activeSucc) && + (activeSucc.prev == activePred) && + (isFirst ? activePred.prev == null : activePred.isAlive()) && + (isLast ? activeSucc.next == null : activeSucc.isAlive())) { + + updateHead(); // Ensure x is not reachable from head + updateTail(); // Ensure x is not reachable from tail + + // Finally, actually gc-unlink + x.lazySetPrev(isFirst ? PREV_TERMINATOR : x); + x.lazySetNext(isLast ? NEXT_TERMINATOR : x); + } + } + } + + /** + * Unlinks non-null first node. + */ + private void unlinkFirst(Finalizer first, Finalizer next) { + // assert first != null; + // assert next != null; + // assert first.item == null; + for (Finalizer o = null, p = next, q;;) { + if (p.isAlive() || (q = p.next) == null) { + if (o != null && p.prev != p && first.casNext(next, p)) { + skipDeletedPredecessors(p); + if (first.prev == null && + (p.next == null || p.isAlive()) && + p.prev == first) { + + updateHead(); // Ensure o is not reachable from head + updateTail(); // Ensure o is not reachable from tail + + // Finally, actually gc-unlink + o.lazySetNext(o); + o.lazySetPrev(PREV_TERMINATOR); + } + } + return; + } + else if (p == q) + return; + else { + o = p; + p = q; + } + } + } + + /** + * Unlinks non-null last node. + */ + private void unlinkLast(Finalizer last, Finalizer prev) { + // assert last != null; + // assert prev != null; + // assert last.item == null; + for (Finalizer o = null, p = prev, q;;) { + if (p.isAlive() || (q = p.prev) == null) { + if (o != null && p.next != p && last.casPrev(prev, p)) { + skipDeletedSuccessors(p); + if (last.next == null && + (p.prev == null || p.isAlive()) && + p.next == last) { + + updateHead(); // Ensure o is not reachable from head + updateTail(); // Ensure o is not reachable from tail + + // Finally, actually gc-unlink + o.lazySetPrev(o); + o.lazySetNext(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. + Finalizer h, p, q; + restartFromHead: + while ((h = head).isDeleted() && (p = h.prev) != null) { + for (;;) { + if ((q = p.prev) == null || + (q = (p = q).prev) == 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. + Finalizer t, p, q; + restartFromTail: + while ((t = tail).isDeleted() && (p = t.next) != null) { + for (;;) { + if ((q = p.next) == null || + (q = (p = q).next) == 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(Finalizer x) { + whileActive: + do { + Finalizer prev = x.prev; + // assert prev != null; + // assert x != NEXT_TERMINATOR; + // assert x != PREV_TERMINATOR; + Finalizer p = prev; + findActive: + for (;;) { + if (p.isAlive()) + break findActive; + Finalizer q = p.prev; + if (q == null) { + if (p.next == p) + continue whileActive; + break findActive; + } + else if (p == q) + continue whileActive; + else + p = q; + } + + // found active CAS target + if (prev == p || x.casPrev(prev, p)) + return; + + } while (x.isAlive() || x.next == null); + } + + private void skipDeletedSuccessors(Finalizer x) { + whileActive: + do { + Finalizer next = x.next; + // assert next != null; + // assert x != NEXT_TERMINATOR; + // assert x != PREV_TERMINATOR; + Finalizer p = next; + findActive: + for (;;) { + if (p.isAlive()) + break findActive; + Finalizer q = p.next; + if (q == null) { + if (p.prev == p) + continue whileActive; + break findActive; + } + else if (p == q) + continue whileActive; + else + p = q; + } + + // found active CAS target + if (next == p || x.casNext(next, p)) + return; + + } while (x.isAlive() || x.prev == null); + } + + /** + * Returns the successor of p, or the first node if p.next has been + * linked to self, which will only be true if traversing with a + * stale pointer that is now off the list. + */ + Finalizer succ(Finalizer p) { + // TODO: should we skip deleted nodes here? + Finalizer q = p.next; + return (p == q) ? first() : q; + } + + /** + * Returns the predecessor of p, or the last node if p.prev has been + * linked to self, which will only be true if traversing with a + * stale pointer that is now off the list. + */ + Finalizer pred(Finalizer p) { + Finalizer q = p.prev; + return (p == q) ? last() : q; + } + + /** + * Returns the first node, the unique node p for which: + * p.prev == null && p.next != p + * The returned node may or may not be logically deleted. + * Guarantees that head is set to the returned node. + */ + Finalizer first() { + restartFromHead: + for (;;) + for (Finalizer h = head, p = h, q;;) { + if ((q = p.prev) != null && + (q = (p = q).prev) != 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.next == null && p.prev != p + * The returned node may or may not be logically deleted. + * Guarantees that tail is set to the returned node. + */ + Finalizer last() { + restartFromTail: + for (;;) + for (Finalizer t = tail, p = t, q;;) { + if ((q = p.next) != null && + (q = (p = q).next) != 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. + */ + FinalizerList() { + head = tail = new Finalizer(); + } + + // Unsafe mechanics + + private boolean casHead(Finalizer cmp, Finalizer val) { + return UNSAFE.compareAndSwapObject(this, HEAD, cmp, val); + } + + private boolean casTail(Finalizer cmp, Finalizer 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(); + PREV_TERMINATOR.next = PREV_TERMINATOR; + NEXT_TERMINATOR = new Finalizer(); + NEXT_TERMINATOR.prev = NEXT_TERMINATOR; + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class flc = FinalizerList.class; + HEAD = UNSAFE.objectFieldOffset + (flc.getDeclaredField("head")); + TAIL = UNSAFE.objectFieldOffset + (flc.getDeclaredField("tail")); + } catch (Exception e) { + throw new Error(e); + } + } +} --- /dev/null 2015-04-10 13:19:24.023596515 +0200 +++ new/src/java.base/share/classes/java/lang/ref/ReferenceHandling.java 2015-05-28 17:55:25.356603807 +0200 @@ -0,0 +1,119 @@ +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 executing Finalizer(s), Cleaner(s) and + * enqueuing other Reference(s). + */ +final class ReferenceHandling { + + private static final ForkJoinPool pool = createPool(); + + /** + * Starts handling of references. Called once from {@code Reference.} + * after VM has booted. + */ + static void start() { + pool.execute(new PendingChunkPoller()); + } + + /** + * Called from {@link Finalizer#runFinalization()} as part of forked secondary + * finalizer thread to run all pending finalizers. We just help the ForkJoinPool + * by waiting for it to quiesce. + */ + static void runFinalization() { + do {} while (!pool.awaitQuiescence(5, TimeUnit.SECONDS)); + } + + /** + * Creates new ForkJoinPool for handling the references. + */ + private static ForkJoinPool createPool() { + return new ForkJoinPool( + Runtime.getRuntime().availableProcessors(), + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, + true + ); + } + + /** + * An eternal task submitted once during initialization. + * Polls for pending references and dispatches handling tasks. + */ + private static final class PendingChunkPoller extends RecursiveAction { + @Override + protected void compute() { + boolean[] morePending = new boolean[1]; + // this is an eternal task - never ends + while (true) { + Reference chunk = Reference.pollPendingChunk(true, morePending); + if (chunk != null) { + if (morePending[0]) { + // fork a handling task and return for more + new PendingChunkHandler(chunk).fork(); + } else { + // no more pending, so we can handle the chunk directly + Reference.handlePendingChunk(chunk); + } + } + } + } + } + + /** + * A task that handles one chunk of references. + */ + private static final class PendingChunkHandler extends RecursiveAction { + 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); + } + } + } + + /** + * A task for handling a single Finalizer. + */ + static final class FinalizerHandler extends RecursiveAction { + private Finalizer finalizer; + + FinalizerHandler(Finalizer finalizer) { + this.finalizer = finalizer; + } + + @Override + protected void compute() { + Finalizer finalizer = this.finalizer; + if (finalizer != null) { + this.finalizer = null; + finalizer.runFinalizer(); + } + } + + void submit() { + if (Thread.currentThread() instanceof ForkJoinWorkerThread) { + // internal submission + fork(); + } else { + // external submission + pool.submit(this); + } + } + } +} +