< prev index next >

src/java.base/share/classes/java/lang/ref/Reference.java

Print this page

        

@@ -27,11 +27,10 @@
 
 import jdk.internal.vm.annotation.DontInline;
 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
  * operations common to all reference objects.  Because reference objects are
  * implemented in close cooperation with the garbage collector, this class may

@@ -105,28 +104,37 @@
 
     /* 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
      */
-    private transient Reference<T> discovered;  /* used by VM */
+    private transient Reference<?> discovered;  /* used by VM */
 
 
     /* Object used to synchronize with the garbage collector.  The collector
      * must acquire this lock at the beginning of each collection cycle.  It is
      * therefore critical that any code holding this lock complete as quickly
      * 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
      * References to this list, while the Reference-handler thread removes
      * them.  This list is protected by the above lock object. The
      * list uses the discovered field to link its elements.
      */
-    private static Reference<Object> pending = null;
+    private static Reference<?> pending;
+
+    /* Discovery phase counter, guarder by above lock.
+     */
+    private static int discoveryPhase;
+
+    /* Enqueue phase counter, guarded by its own enqueuePhaseLock 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 {
 

@@ -137,89 +145,144 @@
                 throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
             }
         }
 
         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) {
             super(g, null, name, 0, false);
         }
 
         public void run() {
             while (true) {
-                tryHandlePending(true);
+                Reference<?> p = getPendingReferences();
+                enqueuePendingReferences(p);
             }
         }
     }
 
     /**
-     * Try handle pending {@link Reference} if there is one.<p>
-     * 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<Object> r;
-        Cleaner c;
-        try {
+     * Blocks until GC discovers some pending references and hands them to us.
+     *<p>
+     * The {@link #discoveryPhase} counter is incrementer as the chunk of
+     * discovered references is handed over to us and any waiters on
+     * {@link #lock} are notified.
+     *
+     * @return a list of pending references linked via {@link #discovered} field
+     * with {@code null} marking the end of list.
+     */
+    static Reference<?> getPendingReferences() {
+        Reference<?> p;
             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) {
+            while ((p = pending) == null) {
+                try {
                         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...
+                    // 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;
         } catch (InterruptedException x) {
-            // retry
-            return true;
+                    // ignore interrupts
         }
-
-        // Fast path for cleaners
-        if (c != null) {
-            c.clean();
-            return true;
+            }
+            pending = null;
+            // increment discoveryPhase counter and notify waiters
+            discoveryPhase++;
+            lock.notifyAll();
+        }
+        return p;
         }
 
-        ReferenceQueue<? super Object> q = r.queue;
+    /**
+     * Enqueue a list of pending {@link Reference}s linked via {@link #discovered}
+     * field with {@code null} marking the end of list.
+     * <p>
+     * The {@link #enqueuePhase} counter is incremented after all references from
+     * the list have been enqueued and any waiters on {@link #enqueuePhaseLock}
+     * are notified.
+     *
+     * @param p a list of pending references linked via {@link #discovered}
+     *          field with {@code null} marking the end of list
+     */
+    static void enqueuePendingReferences(Reference<?> p) {
+        try {
+            // distribute unhooked pending references to their respective queues
+            while (p != null) {
+                Reference<?> r = p;
+                p = r.discovered;
+                r.discovered = null;
+                @SuppressWarnings("unchecked")
+                ReferenceQueue<Object> q = (ReferenceQueue) r.queue;
         if (q != ReferenceQueue.NULL) q.enqueue(r);
-        return true;
+            }
+        } finally {
+            // increment the enqueuePhase counter and notify waiters
+            synchronized (enqueuePhaseLock) {
+                enqueuePhase++;
+                enqueuePhaseLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * If there are any pending references that have not yet been enqueued, block
+     * until enqueue phase for those references starts and return the phase sequence
+     * number. If enqueue phase has already started or it has already finished and
+     * no new references have been discovered by the time this method is called,
+     * just return the phase sequence number immediately.
+     *
+     * @return the phase sequence number of the in-progress or already finished
+     *         enqueue phase.
+     * @throws InterruptedException if interrupted while waiting.
+     */
+    private static int awaitEnqueuePhaseStart() throws InterruptedException {
+        synchronized (lock) {
+            while (pending != null) {
+                lock.wait();
+            }
+            return discoveryPhase;
+        }
+    }
+
+    /**
+     * Block until given enqueue {@code phase} ends. This method guarantees that
+     * all references discovered by the time {@link #awaitEnqueuePhaseStart()}
+     * had been called which returned the phase sequence number that was passed
+     * to this method as {@code phase} parameter, have been enqueued before
+     * returning normally.
+     *
+     * @param phase the enqueue phase sequence number returned from
+     *              {@link #awaitEnqueuePhaseStart()} method.
+     * @throws InterruptedException if interrupted while waiting.
+     */
+    private static void awaitEnqueuePhaseEnd(int phase) throws InterruptedException {
+        synchronized (enqueuePhaseLock) {
+            while (enqueuePhase - phase < 0) {
+                enqueuePhaseLock.wait();
+            }
+        }
+    }
+
+    /**
+     * Triggers discovery of new Reference(s) and waits until they have been
+     * enqueued into their respective queues.
+     *
+     * @throws InterruptedException if interrupted while waiting.
+     */
+    static void discoverAndEnqueueReferences() throws InterruptedException {
+        // trigger discovery of new Reference(s)
+        System.gc();
+        // block until newly discovered references (if any) have been enqueued
+        int phase = awaitEnqueuePhaseStart();
+        awaitEnqueuePhaseEnd(phase);
     }
 
     static {
         ThreadGroup tg = Thread.currentThread().getThreadGroup();
         for (ThreadGroup tgn = tg;

@@ -233,13 +296,19 @@
         handler.setDaemon(true);
         handler.start();
 
         // provide access in SharedSecrets
         SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
+
+            @Override
+            public void discoverAndEnqueueReferences() throws InterruptedException {
+                Reference.discoverAndEnqueueReferences();
+            }
+
             @Override
-            public boolean tryHandlePendingReference() {
-                return tryHandlePending(false);
+            public boolean cleanNextEnqueuedCleanable(Cleaner cleaner) {
+                return cleaner.cleanNextEnqueued();
             }
         });
     }
 
     /* -- Referent accessor and setters -- */
< prev index next >