< 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 >