--- old/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-06 13:37:17.356648920 +0100 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-06 13:37:17.275650315 +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 @@ -126,6 +125,11 @@ */ private static Reference pending = null; + /* Phase counters. Guarded by the above lock object. + */ + private static int unhookPhase; + private static int enqueuePhase; + /* High-priority thread to enqueue pending References */ private static class ReferenceHandler extends Thread { @@ -139,11 +143,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 +155,91 @@ public void run() { while (true) { - tryHandlePending(true); + enqueuePendingReferences(true); } } } /** - * 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 pending {@link Reference}s if GC has discovered any.

+ * This method does not return until all references that had been discovered + * by the time this method was called, are enqueued even if some of them are + * being enqueued concurrently by other threads. + * + * @param waitForNotifyFromGc if {@code true} and there is no pending + * {@link Reference}, wait until VM discovers some + * or interrupted (in which case the interrupted + * status is cleared); if {@code false} and there + * is no pending reference, just wait until threads + * that found some discovered references before + * us enqueue them all. + */ + static void enqueuePendingReferences(boolean waitForNotifyFromGc) { + Reference p; + int unhookedInPhase; 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 && waitForNotifyFromGc) { + lock.wait(); } + // unhook the whole chain of pending References at once + pending = null; + // remember the phase in which we unhooked a chain + // of pending references and increment the counter + unhookedInPhase = unhookPhase++; } } 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; } - // Fast path for cleaners - if (c != null) { - c.clean(); - return true; + 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 { + boolean interrupted = false; + synchronized (lock) { + // wait for concurrent enqueuing threads that unhooked + // pending references before us to finish with enqueueing + // before proceeding... + try { + while (unhookedInPhase - enqueuePhase > 0) { + try { + lock.wait(); + } catch (OutOfMemoryError e) { + // The waiting on the lock may cause an OutOfMemoryError + // (we swallow such interrupt as we are not sure about + // OOME cause). + Thread.yield(); + } catch (InterruptedException e) { + // remember that we were interrupted + interrupted = true; + } + } + } finally { + // increment enqueue phase counter + enqueuePhase++; + // notify waiters + lock.notifyAll(); + } + } + // re-assert thread interrupted status + if (interrupted) { + Thread.currentThread().interrupt(); + } } - - ReferenceQueue q = r.queue; - if (q != ReferenceQueue.NULL) q.enqueue(r); - return true; } static { @@ -236,8 +258,13 @@ // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override - public boolean tryHandlePendingReference() { - return tryHandlePending(false); + public void enqueuePendingReferences() { + Reference.enqueuePendingReferences(false); + } + + @Override + public boolean cleanNextEnqueuedCleanable(Cleaner cleaner) { + return cleaner.cleanNextEnqueued(); } }); }