--- old/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-04-01 17:59:16.422081454 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-04-01 17:59:16.324080835 +0200 @@ -25,133 +25,237 @@ package jdk.internal.ref; +import jdk.internal.misc.InnocuousThread; +import jdk.internal.misc.JavaLangRefAccess; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.vm.annotation.ReservedStackAccess; + import java.lang.ref.Cleaner; -import java.lang.ref.Cleaner.Cleanable; import java.lang.ref.ReferenceQueue; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Objects; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; - -import jdk.internal.misc.InnocuousThread; +import java.util.concurrent.locks.StampedLock; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; /** - * CleanerImpl manages a set of object references and corresponding cleaning actions. - * CleanerImpl provides the functionality of {@link java.lang.ref.Cleaner}. + * CleanerImpl is the implementation of {@link Cleaner}. */ -public final class CleanerImpl implements Runnable { +public class CleanerImpl implements Cleaner { - /** - * An object to access the CleanerImpl from a Cleaner; set by Cleaner init. - */ - private static Function cleanerImplAccess = null; + final Task task; + + public CleanerImpl(ThreadFactory threadFactory) { + task = new Task(); + task.start(this, threadFactory); + } + + @Override + public Cleanable register(Object obj, Runnable action) { + Objects.requireNonNull(obj, "obj"); + Objects.requireNonNull(action, "action"); + return new CleanerImpl.PhantomCleanableRef(obj, this, action); + } /** - * Heads of a CleanableList for each reference type. + * CleanerImpl.ExtendedImpl is the implementation of {@link ExtendedCleaner}. */ - final PhantomCleanable phantomCleanableList; + static class ExtendedImpl extends CleanerImpl implements ExtendedCleaner { - final WeakCleanable weakCleanableList; + ExtendedImpl(ThreadFactory threadFactory) { + super(threadFactory); + } - final SoftCleanable softCleanableList; + // A fair lock for threads that retry actions to queue after + // 1st optimistic try fails so that only a single thread at a time is + // retrying actions while helping the Cleaner execute Cleanable(s) + // and trigger new Reference discovery before finally giving up. + private final StampedLock helpingLock = new StampedLock(); + + @Override + public T retryWhileHelpingClean(Supplier retriableAction) { + T result; + // 1st optimistic try - allow concurrent execution of actions + // until helping is necessary + long stamp = helpingLock.tryReadLock(); + if (stamp != 0) try { + if ((result = retriableAction.get()) != null) { + return result; + } + } finally { + helpingLock.unlockRead(stamp); + } - // The ReferenceQueue of pending cleaning actions - final ReferenceQueue queue; + // retrials with helping are exclusive + stamp = helpingLock.writeLock(); + try { + // retry action while executing enqueued Cleanable(s) until the + // queue drains out + do { + if ((result = retriableAction.get()) != null) { + return result; + } + } while (task.cleanNextEnqueued()); + + // the queue is now empty but there may have been a long time + // since last Reference discovery has been performed. + JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); + // trigger Reference(s) discovery + int discoveryPhase = jlra.discoverReferences(); + // wait for newly discovered Reference(s) to be enqueued + boolean interrupted = false; + try { + while (true) { + try { + jlra.awaitReferencesEnqueued(discoveryPhase); + break; + } catch (InterruptedException e) { + // ignore interrupts but don't swallow them + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } - /** - * Called by Cleaner static initialization to provide the function - * to map from Cleaner to CleanerImpl. - * @param access a function to map from Cleaner to CleanerImpl - */ - public static void setCleanerImplAccess(Function access) { - if (cleanerImplAccess == null) { - cleanerImplAccess = access; - } else { - throw new InternalError("cleanerImplAccess"); + // the queue is now hopefully filled with some freshly discovered + // Cleanable(s) so retry operation while executing enqueued Cleanable(s) + // until the queue drains out + do { + if ((result = retriableAction.get()) != null) { + return result; + } + } while (task.cleanNextEnqueued()); + + // give up finally + return null; + } finally { + helpingLock.unlockWrite(stamp); + } } } - /** - * Called to get the CleanerImpl for a Cleaner. - * @param cleaner the cleaner - * @return the corresponding CleanerImpl - */ - static CleanerImpl getCleanerImpl(Cleaner cleaner) { - return cleanerImplAccess.apply(cleaner); - } + // package-private access to Task's state + PhantomCleanable phantomCleanableList() { return task.phantomCleanableList; } + WeakCleanable weakCleanableList() { return task.weakCleanableList; } + SoftCleanable softCleanableList() { return task.softCleanableList; } + ReferenceQueue queue() { return task.queue; } + + /** + * CleanerImpl.Task manages a set of object references and corresponding + * cleaning actions and executes them after they are enqueued. + */ + private static final class Task implements Runnable { + /** + * Heads of a CleanableList for each reference type. + */ + final PhantomCleanable phantomCleanableList; + + final WeakCleanable weakCleanableList; + + final SoftCleanable softCleanableList; + + // The ReferenceQueue of pending cleaning actions + final ReferenceQueue queue; + + /** + * Constructor for Task. + */ + Task() { + queue = new ReferenceQueue<>(); + phantomCleanableList = new PhantomCleanableRef(); + weakCleanableList = new WeakCleanableRef(); + softCleanableList = new SoftCleanableRef(); + } - /** - * Constructor for CleanerImpl. - */ - public CleanerImpl() { - queue = new ReferenceQueue<>(); - phantomCleanableList = new PhantomCleanableRef(); - weakCleanableList = new WeakCleanableRef(); - softCleanableList = new SoftCleanableRef(); - } + /** + * Starts the Cleaner implementation. + * Ensure this is the CleanerImpl for the Cleaner. + * When started waits for Cleanables to be queued. + * @param cleaner the cleaner + * @param threadFactory the thread factory + */ + void start(CleanerImpl cleaner, ThreadFactory threadFactory) { + if (cleaner.task != this) { + throw new AssertionError("wrong cleaner"); + } + // schedule a nop cleaning action for the cleaner, so the associated thread + // will continue to run at least until the cleaner is reclaimable. + new CleanerCleanable(cleaner); - /** - * Starts the Cleaner implementation. - * Ensure this is the CleanerImpl for the Cleaner. - * When started waits for Cleanables to be queued. - * @param cleaner the cleaner - * @param threadFactory the thread factory - */ - public void start(Cleaner cleaner, ThreadFactory threadFactory) { - if (getCleanerImpl(cleaner) != this) { - throw new AssertionError("wrong cleaner"); - } - // schedule a nop cleaning action for the cleaner, so the associated thread - // will continue to run at least until the cleaner is reclaimable. - new CleanerCleanable(cleaner); - - if (threadFactory == null) { - threadFactory = CleanerImpl.InnocuousThreadFactory.factory(); - } - - // now that there's at least one cleaning action, for the cleaner, - // we can start the associated thread, which runs until - // all cleaning actions have been run. - Thread thread = threadFactory.newThread(this); - thread.setDaemon(true); - thread.start(); - } + if (threadFactory == null) { + threadFactory = CleanerImpl.InnocuousThreadFactory.factory(); + } - /** - * Process queued Cleanables as long as the cleanable lists are not empty. - * A Cleanable is in one of the lists for each Object and for the Cleaner - * itself. - * Terminates when the Cleaner is no longer reachable and - * has been cleaned and there are no more Cleanable instances - * for which the object is reachable. - *

- * If the thread is a ManagedLocalsThread, the threadlocals - * are erased before each cleanup - */ - @Override - public void run() { - Thread t = Thread.currentThread(); - InnocuousThread mlThread = (t instanceof InnocuousThread) - ? (InnocuousThread) t - : null; - while (!phantomCleanableList.isListEmpty() || - !weakCleanableList.isListEmpty() || - !softCleanableList.isListEmpty()) { - if (mlThread != null) { - // Clear the thread locals - mlThread.eraseThreadLocals(); + // now that there's at least one cleaning action, for the cleaner, + // we can start the associated thread, which runs until + // all cleaning actions have been run. + Thread thread = threadFactory.newThread(this); + thread.setDaemon(true); + thread.start(); + } + + /** + * Process queued Cleanables as long as the cleanable lists are not empty. + * A Cleanable is in one of the lists for each Object and for the Cleaner + * itself. + * Terminates when the Cleaner is no longer reachable and + * has been cleaned and there are no more Cleanable instances + * for which the object is reachable. + *

+ * If the thread is a ManagedLocalsThread, the threadlocals + * are erased before each cleanup + */ + @Override + public void run() { + Thread t = Thread.currentThread(); + InnocuousThread mlThread = (t instanceof InnocuousThread) + ? (InnocuousThread) t + : null; + while (!phantomCleanableList.isListEmpty() || + !weakCleanableList.isListEmpty() || + !softCleanableList.isListEmpty()) { + if (mlThread != null) { + // Clear the thread locals + mlThread.eraseThreadLocals(); + } + try { + // Wait for a Ref, with a timeout to avoid getting hung + // due to a race with clear/clean + Cleanable ref = (Cleanable) queue.remove(60 * 1000L); + if (ref != null) { + ref.clean(); + } + } catch (Throwable e) { + // ignore exceptions from the cleanup action + // (including interruption of cleanup thread) + } } - try { - // Wait for a Ref, with a timeout to avoid getting hung - // due to a race with clear/clean - Cleanable ref = (Cleanable) queue.remove(60 * 1000L); - if (ref != null) { + } + + /** + * Processes next Cleanable that has been waiting in the queue. + * + * @return {@code true} if a Cleanable was found in the queue and + * was processed or {@code false} if the queue was empty. + */ + @ReservedStackAccess + boolean cleanNextEnqueued() { + Cleanable ref = (Cleanable) queue.poll(); + if (ref != null) { + try { ref.clean(); + } catch (Throwable t) { + // ignore exceptions from the cleanup action } - } catch (Throwable e) { - // ignore exceptions from the cleanup action - // (including interruption of cleanup thread) + return true; + } else { + return false; } } }