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