--- old/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-19 00:16:19.665454803 +0100 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-19 00:16:19.603455838 +0100 @@ -29,7 +29,6 @@ import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangRefAccess; import jdk.internal.misc.SharedSecrets; -import jdk.internal.ref.Cleaner; /** * Abstract base class for reference objects. This class defines the @@ -116,7 +115,7 @@ * as possible, allocate no new objects, and avoid calling user code. */ private static 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 @@ -126,6 +125,15 @@ */ private static Reference pending = null; + /* Unhook phase counter. Guarded by the above lock object. + */ + private static int unhookPhase; + + /* Enqueue phase counter, guarded by its own lock object. + */ + private static int enqueuePhase; + private static final Object enqueuePhaseLock = new Object(); + /* High-priority thread to enqueue pending References */ private static class ReferenceHandler extends Thread { @@ -139,11 +147,10 @@ } static { - // pre-load and initialize InterruptedException and Cleaner classes + // pre-load and initialize InterruptedException class // so that we don't get into trouble later in the run loop if there's - // memory shortage while loading/initializing them lazily. + // memory shortage while loading/initializing it lazily. ensureClassInitialized(InterruptedException.class); - ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { @@ -152,72 +159,90 @@ public void run() { while (true) { - tryHandlePending(true); + enqueuePendingReferences(); } } } /** - * 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 - * {@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; + * Enqueue a chunk of pending {@link Reference}s if GC has discovered any + * or wait for GC to discover and notify us and then enqueue them. + *

+ * The unhookPhase and enqueuePhase counters are incremented as the chunk of + * pending References is unhooked and after it is enqueued respectively so + * that any waiters can find out when the references that were pending upon + * the call to {@link #awaitPendingReferencesEnqueued()} have been enqueued. + *

+ * Guarantees given by {@link #awaitPendingReferencesEnqueued()} method can + * only be respected if this method is executed by a single thread - + * the ReferenceHandler thread. + */ + static void enqueuePendingReferences() { + Reference p; 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(); - } - // retry if waited - return waitForNotify; + while ((p = pending) == null) { + lock.wait(); } + // unhook the whole chain of pending References at once + pending = null; + // increment the unhookPhase counter and notify waiters + unhookPhase++; + lock.notifyAll(); } } 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... + // The waiting on the lock may cause an OutOfMemoryError + // because it may try to allocate InterruptedException object. + // Give other threads CPU time so they hopefully drop some live + // references and GC reclaims some space. Thread.yield(); - // retry - return true; + return; } catch (InterruptedException x) { - // retry - return true; + return; + } + + try { + // distribute unhooked pending references to their respective queues + while (p != null) { + Reference r = p; + p = r.discovered; + r.discovered = null; + ReferenceQueue q = r.queue; + if (q != ReferenceQueue.NULL) q.enqueue(r); + } + } finally { + // increment the enqueuePhase counter and notify waiters + synchronized (enqueuePhaseLock) { + enqueuePhase++; + enqueuePhaseLock.notifyAll(); + } } + } - // Fast path for cleaners - if (c != null) { - c.clean(); - return true; + /** + * Blocks until all Reference(s) that were pending at the time this method + * was called have been enqueued. + * + * @throws InterruptedException if the thread is interrupted while waiting. + */ + static void awaitPendingReferencesEnqueued() throws InterruptedException { + int unhookPhaseSnapshot; + synchronized (lock) { + // wait for ReferenceHandler to unhook the chunk of pending references + while (pending != null) { + lock.wait(); + } + // remember the phase when after our arrival the chunk of pending + // references has been unhooked + unhookPhaseSnapshot = unhookPhase; } - ReferenceQueue q = r.queue; - if (q != ReferenceQueue.NULL) q.enqueue(r); - return true; + synchronized (enqueuePhaseLock) { + // wait for ReferenceHandler to enqueue those references + while (unhookPhaseSnapshot - enqueuePhase > 0) { + enqueuePhaseLock.wait(); + } + } } static { @@ -236,8 +261,13 @@ // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override - public boolean tryHandlePendingReference() { - return tryHandlePending(false); + public void awaitPendingReferencesEnqueued() throws InterruptedException { + Reference.awaitPendingReferencesEnqueued(); + } + + @Override + public boolean cleanNextEnqueuedCleanable(Cleaner cleaner) { + return cleaner.cleanNextEnqueued(); } }); }