< prev index next >
src/java.base/share/classes/java/lang/ref/Reference.java
Print this page
*** 97,134 ****
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
! @SuppressWarnings("rawtypes")
! 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<T> 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.
*/
static private class Lock { }
! private static 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;
! /* High-priority thread to enqueue pending References
*/
private static class ReferenceHandler extends ManagedLocalsThread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
--- 97,145 ----
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
! 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 */
/* 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.
*/
static private class 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;
! /* Max. number of Reference(s) to unhook from pending chain in one chunk
! * before releasing the lock, handling them and grabbing the
! * lock again.
*/
+ private static final int UNHOOK_CHUNK_SIZE = 32768;
+
+ /* Max. number of Finalizer(s) to execute in one ForkJoinTask
+ */
+ private static final int FINALIZE_CHUNK_SIZE = 256;
+
+ /* Max. number of Reference(s) to enqueue in one chunk
+ */
+ private static final int ENQUEUE_CHUNK_SIZE = 256;
+
private static class ReferenceHandler extends ManagedLocalsThread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
*** 148,246 ****
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
! tryHandlePending(true);
}
}
}
/**
! * 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 {
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;
}
}
! } 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;
! }
!
! // Fast path for cleaners
! if (c != null) {
! c.clean();
! return true;
! }
!
! ReferenceQueue<? super Object> q = r.queue;
! if (q != ReferenceQueue.NULL) q.enqueue(r);
! return true;
}
! 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);
}
- });
}
/* -- Referent accessor and setters -- */
/**
--- 159,370 ----
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
+ // wait for VM to boot-up before starting reference handling
+ // ForkJoinPool since it needs access to some system properties
+ // and Finalizer needs access to SharedSecrets.
while (true) {
! try {
! sun.misc.VM.awaitBooted();
! break;
! } catch (InterruptedException e) {
! // ignore;
! }
! }
! // start reference handling ForkJoinPool
! ReferenceHandling.start();
! // enter endless loop
! boolean[] morePending = new boolean[1];
! while (true) {
! Reference<?> chunk = null;
! try {
! synchronized (lock) {
! chunk = Reference.unhookPendingChunk(UNHOOK_CHUNK_SIZE, morePending);
! if (chunk == null) {
! // waiting on notification can throw InterruptedException
! // if the thread is interrupted, but also OutOfMemoryError
! // if the InterruptedException can not be allocated.
! lock.wait();
! // since we have already re-obtained the lock, we can
! // re-try poll and will typically get a non-null chunk.
! chunk = Reference.unhookPendingChunk(UNHOOK_CHUNK_SIZE, morePending);
! }
! }
! } catch (OutOfMemoryError e) {
! // give other threads some time so they hopefully release some
! // references and GC reclaims some space, then retry...
! Thread.yield();
! } catch (InterruptedException e) {
! // ignore
! }
! if (chunk != null) {
! if (morePending[0]) {
! // submit a handling task and return for next chunk
! new ReferenceHandling.PendingChunkHandler(chunk).submit();
! } else {
! // no more pending, so we can handle the chunk directly
! Reference.handlePendingChunk(chunk);
! }
! }
}
}
}
/**
! * Try handle a chunk of pending {@link Reference}s if there are any.<p>
! * 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.
*
! * @return {@code true} if there is be more {@link Reference}s pending.
! */
! static boolean tryHandlePending() {
! Reference<?> r;
! synchronized (lock) {
! r = unhookPendingChunk(UNHOOK_CHUNK_SIZE, null);
! }
! if (r == null) return false;
! handlePendingChunk(r);
synchronized (lock) {
! return pending != null;
! }
! }
!
! /**
! * Unhooks a chunk of max. {@code chunkSize} references from pending chain and
! * returns the head of the chunk; elements of the chunk can be reached using
! * {@link #next} links; the last in chunk is linked to itself.
! *
! * @param chunkSize max. number of references to unhook from the pending chain
! * @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. {@code chunkSize} pending references or
! * null if there are none pending.
! */
! private static Reference<?> unhookPendingChunk(int chunkSize, boolean[] morePending) {
! // assert Thread.holdsLock(lock);
! Reference<?> r;
! 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 >= chunkSize) {
! break;
}
! rd.next = r;
! r = rd;
}
+ pending = (Reference) rd;
+ if (morePending != null) morePending[0] = (rd != null);
+ } else {
+ if (morePending != null) morePending[0] = false;
}
! return r;
}
! /**
! * Handles a non-null chunk of pending references
! * (obtained using {@link #unhookPendingChunk}) and handles
! * them as following:
! * <ul>
! * <li>Cleaner(s) are executed immediately</li>
! * <li>Finalizer(s) are submitted as ForkJoinTask(s)</li>
! * <li>all other Reference(s) are enqueued in their respected queues</li>
! * </ul>
! * @param chunk the head of a chunk of pending references
! */
! static void handlePendingChunk(Reference<?> chunk) {
! // batch finaliz(ato|e)rs and Finalizators
! Reference<?> finalizrs = null;
! int finalizrsCount = 0;
! // batch consecutive references with same queue into chunks
! Reference<?> referencesHead = null, referencesTail = null;
! int referencesCount = 0;
! ReferenceQueue<?> referenceQueue = null;
! // dispatch references to appropriate targets
! for (Reference<?> r = chunk, rn = r.next; ; r = rn, rn = r.next) {
! if (r instanceof Cleaner) { // Fast path for cleaners
! // take 'r' off the chain
! r.next = r;
! ((Cleaner) r).clean();
! } else if (r instanceof Finalizer ||
! r instanceof Finalizator) { // Submit task(s) for finaliz(ato|e)rs
! // hook onto the finalizers chain
! r.next = (finalizrs == null) ? r : finalizrs;
! finalizrs = r;
! if (++finalizrsCount >= FINALIZE_CHUNK_SIZE) {
! // when chunk of finaliz(ato|e)rs is full, submit a task
! new ReferenceHandling.FinalizrHandler(finalizrs).submit();
! finalizrs = null;
! finalizrsCount = 0;
! }
! } else { // Enqueue all other references
! // take 'r' off the chain
! r.next = r;
! ReferenceQueue<?> q = r.queue;
! if (q != ReferenceQueue.NULL && q.markEnqueued(r)) { // markEnqueued is atomic
! if (referenceQueue == null || referenceQueue == q) {
! // no queue or same queue -> hook onto the references[Head|Tail] chain
! if (referencesHead == null) {
! // assert referencesTail == null && referenceQueue == null &&
! // referencesCount == 0 && r.next == r;
! referenceQueue = q;
! referencesHead = referencesTail = r;
! } else {
! // assert referencesTail != null && referenceQueue == q &&
! // referencesCount > 0;
! r.next = referencesHead;
! referencesHead = r;
! }
! if (++referencesCount >= ENQUEUE_CHUNK_SIZE) {
! // when a chunk of references is full, add them to queue
! referenceQueue.addChunk(referencesHead, referencesTail);
! referencesHead = referencesTail = null;
! referenceQueue = null;
! referencesCount = 0;
! }
! } else {
! // when a different queue is encountered,
! // add collected chunk to it's queue and start collecting
! // into new queue...
! // assert referenceQueue != null && referenceQueue != q &&
! // referencesHead != null && referencesTail != null &&
! // referencesCount > 0 && r.next == r;
! referenceQueue.addChunk(referencesHead, referencesTail);
! referenceQueue = q;
! referencesHead = referencesTail = r;
! referencesCount = 1;
! }
! }
! }
! if (rn == r) { // last in chain
! break;
! }
! }
! // any finalizers left?
! if (finalizrs != null) {
! new ReferenceHandling.FinalizrHandler(finalizrs).submit();
! finalizrs = null;
! finalizrsCount = 0;
! }
! // any references left to enqueue?
! if (referenceQueue != null) {
! // assert referencesHead != null && referencesTail != null && referencesCount > 0;
! referenceQueue.addChunk(referencesHead, referencesTail);
! referencesHead = referencesTail = null;
! referenceQueue = null;
! referencesCount = 0;
}
}
/* -- Referent accessor and setters -- */
/**
*** 307,312 ****
--- 431,489 ----
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
+ // 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<? super T> val) {
+ UNSAFE.putOrderedObject(this, queueOffset, val);
+ }
+
+ boolean casQueue(ReferenceQueue<?> cmp, ReferenceQueue<? super T> 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<Reference> rc = Reference.class;
+ referentOffset = UNSAFE.objectFieldOffset(rc.getDeclaredField("referent"));
+ queueOffset = UNSAFE.objectFieldOffset(rc.getDeclaredField("queue"));
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+
+ 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();
+ }
+ });
+ }
}
< prev index next >