--- old/src/java.base/share/classes/java/lang/ref/Cleaner.java 2016-03-28 16:44:45.968089323 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Cleaner.java 2016-03-28 16:44:45.878090881 +0200 @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.concurrent.ThreadFactory; -import java.util.function.Function; /** * {@code Cleaner} manages a set of object references and corresponding cleaning actions. @@ -128,28 +127,7 @@ * All cleaning actions registered to a cleaner should be mutually compatible. * @since 9 */ -public final class Cleaner { - - /** - * The Cleaner implementation. - */ - final CleanerImpl impl; - - static { - CleanerImpl.setCleanerImplAccess(new Function() { - @Override - public CleanerImpl apply(Cleaner cleaner) { - return cleaner.impl; - } - }); - } - - /** - * Construct a Cleaner implementation and start it. - */ - private Cleaner() { - impl = new CleanerImpl(); - } +public interface Cleaner { /** * Returns a new {@code Cleaner}. @@ -170,10 +148,8 @@ * @throws SecurityException if the current thread is not allowed to * create or start the thread. */ - public static Cleaner create() { - Cleaner cleaner = new Cleaner(); - cleaner.impl.start(cleaner, null); - return cleaner; + static Cleaner create() { + return new CleanerImpl(null); } /** @@ -197,11 +173,9 @@ * @throws SecurityException if the current thread is not allowed to * create or start the thread. */ - public static Cleaner create(ThreadFactory threadFactory) { + static Cleaner create(ThreadFactory threadFactory) { Objects.requireNonNull(threadFactory, "threadFactory"); - Cleaner cleaner = new Cleaner(); - cleaner.impl.start(cleaner, threadFactory); - return cleaner; + return new CleanerImpl(threadFactory); } /** @@ -214,18 +188,14 @@ * @param action a {@code Runnable} to invoke when the object becomes phantom reachable * @return a {@code Cleanable} instance */ - public Cleanable register(Object obj, Runnable action) { - Objects.requireNonNull(obj, "obj"); - Objects.requireNonNull(action, "action"); - return new CleanerImpl.PhantomCleanableRef(obj, this, action); - } + Cleanable register(Object obj, Runnable action); /** * {@code Cleanable} represents an object and a * cleaning action registered in a {@code Cleaner}. * @since 9 */ - public interface Cleanable { + interface Cleanable { /** * Unregisters the cleanable and invokes the cleaning action. * The cleanable's cleaning action is invoked at most once --- old/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-28 16:44:46.218084995 +0200 +++ new/src/java.base/share/classes/java/lang/ref/Reference.java 2016-03-28 16:44:46.112086830 +0200 @@ -25,11 +25,10 @@ package java.lang.ref; -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; +import jdk.internal.vm.annotation.DontInline; /** * Abstract base class for reference objects. This class defines the @@ -107,7 +106,7 @@ * pending: next element in the pending list (or null if last) * otherwise: NULL */ - private transient Reference discovered; /* used by VM */ + private transient Reference discovered; /* used by VM */ /* Object used to synchronize with the garbage collector. The collector @@ -116,7 +115,7 @@ * 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 @@ -124,7 +123,16 @@ * them. This list is protected by the above lock object. The * list uses the discovered field to link its elements. */ - private static Reference pending = null; + private static Reference pending; + + /* Discovery phase counter, guarder by above lock. + */ + private static int discoveryPhase; + + /* Enqueue phase, guarded by enqueuePhaseLock object. + */ + private static int enqueuePhase; + private static final Object enqueuePhaseLock = new Object(); /* High-priority thread to enqueue pending References */ @@ -139,11 +147,10 @@ } 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) { @@ -151,73 +158,118 @@ } public void run() { + int[] discoveryPhase = new int[1]; while (true) { - tryHandlePending(true); + Reference p = getPendingReferences(discoveryPhase); + enqueuePendingReferences(p, discoveryPhase[0]); } } } /** - * Try handle pending {@link Reference} if there is one.

- * 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 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 { + * Blocks until GC discovers some pending references, sets the 0-th element + * of given {@code discoveryPhaseHolder} to the phase in which they were + * discovered and returns the head of the list of discovered references. + * + * @param discoveryPhaseHolder a 1-element array to hold the returned + * discovery phase. + * @return the head of a list of pending references linked via + * {@link #discovered} field with {@code null} marking the end of list. + */ + static Reference getPendingReferences(int[] discoveryPhaseHolder) { + Reference p; + synchronized (lock) { + while ((p = pending) == null) { + try { + lock.wait(); + } catch (OutOfMemoryError x) { // 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; + // 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(); + } catch (InterruptedException x) { + // ignore interrupts } } - } 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 q = r.queue; - if (q != ReferenceQueue.NULL) q.enqueue(r); - return true; + pending = null; + // increment discoveryPhase counter and return it in a holder array + discoveryPhaseHolder[0] = ++discoveryPhase; + } + return p; + } + + /** + * @return current finished discovery phase. + */ + static int getDiscoveryPhase() { + synchronized (lock) { + return (pending == null) + ? discoveryPhase // already incremented + : discoveryPhase + 1; // not yet incremented + } + } + + /** + * Enqueue a list of pending {@link Reference}s linked via {@link #discovered} + * field with {@code null} marking the end of list. + *

+ * The {@link #enqueuePhase} is set to given {@code discoveryPhase} + * 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. + * @param discoveryPhase the phase in which given references were discovered. + */ + static void enqueuePendingReferences(Reference p, int discoveryPhase) { + try { + // distribute unhooked pending references to their respective queues + while (p != null) { + Reference r = p; + p = r.discovered; + r.discovered = null; + @SuppressWarnings("unchecked") + ReferenceQueue q = (ReferenceQueue) r.queue; + if (q != ReferenceQueue.NULL) q.enqueue(r); + } + } finally { + // mark the enqueueing of references discovered in given + // discovery phase is finished and notify waiters. + synchronized (enqueuePhaseLock) { + enqueuePhase = discoveryPhase; + enqueuePhaseLock.notifyAll(); + } + } + } + + /** + * Triggers discovery of new Reference(s) and returns the phase sequence number + * in which they were discovered or previous phase sequence number if no new + * Reference(s) were discovered. + */ + static int discoverReferences() { + // trigger discovery of new Reference(s) + System.gc(); + // obtain the phase in which they were discovered (if any) + return getDiscoveryPhase(); + } + + /** + * Blocks until all Reference(s) that were discovered in given + * {@code discoveryPhase} (as returned by {@link #discoverReferences()}) + * have been enqueued. + * + * @param discoveryPhase the discovery phase sequence number. + * @throws InterruptedException if interrupted while waiting. + */ + static void awaitReferencesEnqueued(int discoveryPhase) throws InterruptedException { + // await for them to be enqueued + synchronized (enqueuePhaseLock) { + while (enqueuePhase - discoveryPhase < 0) { + enqueuePhaseLock.wait(); + } + } } static { @@ -236,8 +288,14 @@ // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override - public boolean tryHandlePendingReference() { - return tryHandlePending(false); + public int discoverReferences() { + return Reference.discoverReferences(); + } + + @Override + public void awaitReferencesEnqueued( + int discoveryPhase) throws InterruptedException { + Reference.awaitReferencesEnqueued(discoveryPhase); } }); } --- old/src/java.base/share/classes/java/nio/Bits.java 2016-03-28 16:44:46.497080164 +0200 +++ new/src/java.base/share/classes/java/nio/Bits.java 2016-03-28 16:44:46.398081878 +0200 @@ -25,13 +25,14 @@ package java.nio; -import java.util.concurrent.atomic.AtomicLong; - import jdk.internal.misc.JavaNioAccess; -import jdk.internal.misc.JavaLangRefAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.ref.CleanerFactory; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BooleanSupplier; /** * Access to bits, native and otherwise. @@ -603,12 +604,6 @@ private static final AtomicLong count = new AtomicLong(); private static volatile boolean memoryLimitSet; - // max. number of sleeps during try-reserving with exponentially - // increasing delay before throwing OutOfMemoryError: - // 1, 2, 4, 8, 16, 32, 64, 128, 256 (total 511 ms ~ 0.5 s) - // which means that OOME will be thrown after 0.5 s of trying - private static final int MAX_SLEEPS = 9; - // These methods should be called whenever direct memory is allocated or // freed. They allow the user to control the amount of direct memory // which a process may access. All sizes are specified in bytes. @@ -619,57 +614,15 @@ memoryLimitSet = true; } - // optimist! - if (tryReserveMemory(size, cap)) { - return; - } - - final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); - - // retry while helping enqueue pending Reference objects - // which includes executing pending Cleaner(s) which includes - // Cleaner(s) that free direct buffer memory - while (jlra.tryHandlePendingReference()) { - if (tryReserveMemory(size, cap)) { - return; - } - } - - // trigger VM's Reference processing - System.gc(); - - // a retry loop with exponential back-off delays - // (this gives VM some time to do it's job) - boolean interrupted = false; - try { - long sleepTime = 1; - int sleeps = 0; - while (true) { - if (tryReserveMemory(size, cap)) { - return; - } - if (sleeps >= MAX_SLEEPS) { - break; + if (!CleanerFactory.cleaner().retryWhileHelpingClean( + new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + return tryReserveMemory(size, cap); } - if (!jlra.tryHandlePendingReference()) { - try { - Thread.sleep(sleepTime); - sleepTime <<= 1; - sleeps++; - } catch (InterruptedException e) { - interrupted = true; - } - } - } - + })) { // no luck throw new OutOfMemoryError("Direct buffer memory"); - - } finally { - if (interrupted) { - // don't swallow interrupts - Thread.currentThread().interrupt(); - } } } --- old/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template 2016-03-28 16:44:46.729076147 +0200 +++ new/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template 2016-03-28 16:44:46.635077775 +0200 @@ -30,7 +30,8 @@ import java.io.FileDescriptor; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; -import jdk.internal.ref.Cleaner; +import java.lang.ref.Cleaner; +import jdk.internal.ref.CleanerFactory; import sun.nio.ch.DirectBuffer; @@ -98,13 +99,13 @@ } - private final Cleaner cleaner; + private final Cleaner.Cleanable cleaner; - public Cleaner cleaner() { return cleaner; } + public Cleaner.Cleanable cleaner() { return cleaner; } #else[byte] - public Cleaner cleaner() { return null; } + public Cleaner.Cleanable cleaner() { return null; } #end[byte] @@ -136,7 +137,7 @@ } else { address = base; } - cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); + cleaner = CleanerFactory.cleaner().register(this, new Deallocator(base, size, cap)); att = null; #else[rw] super(cap); @@ -176,7 +177,9 @@ #if[rw] super(-1, 0, cap, cap, fd); address = addr; - cleaner = Cleaner.create(this, unmapper); + cleaner = (unmapper == null) + ? null + : CleanerFactory.cleaner().register(this, unmapper); att = null; #else[rw] super(cap, addr, fd, unmapper); --- old/src/java.base/share/classes/jdk/internal/misc/JavaLangRefAccess.java 2016-03-28 16:44:46.949072339 +0200 +++ new/src/java.base/share/classes/jdk/internal/misc/JavaLangRefAccess.java 2016-03-28 16:44:46.863073827 +0200 @@ -28,12 +28,19 @@ public interface JavaLangRefAccess { /** - * Help ReferenceHandler thread process next pending - * {@link java.lang.ref.Reference} + * Triggers discovery of new Reference(s) and returns the phase sequence number + * in which they were discovered or previous phase sequence number if no new + * Reference(s) were discovered. + */ + int discoverReferences(); + + /** + * Blocks until all Reference(s) that were discovered in given + * {@code discoveryPhase} (as returned by {@link #discoverReferences()}) + * have been enqueued. * - * @return {@code true} if there was a pending reference and it - * was enqueue-ed or {@code false} if there was no - * pending reference + * @param discoveryPhase the discovery phase sequence number. + * @throws InterruptedException if interrupted while waiting. */ - boolean tryHandlePendingReference(); + void awaitReferencesEnqueued(int discoveryPhase) throws InterruptedException; } --- old/src/java.base/share/classes/jdk/internal/ref/CleanerFactory.java 2016-03-28 16:44:47.197068045 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/CleanerFactory.java 2016-03-28 16:44:47.096069793 +0200 @@ -27,7 +27,6 @@ import jdk.internal.misc.InnocuousThread; -import java.lang.ref.Cleaner; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.concurrent.ThreadFactory; @@ -39,30 +38,37 @@ public final class CleanerFactory { /* The common Cleaner. */ - private final static Cleaner commonCleaner = Cleaner.create(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return AccessController.doPrivileged(new PrivilegedAction<>() { - @Override - public Thread run() { - Thread t = InnocuousThread.newSystemThread("Common-Cleaner", r); - t.setPriority(Thread.MAX_PRIORITY - 2); - return t; - } - }); + private final static ExtendedCleaner commonCleaner = ExtendedCleaner.create( + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public Thread run() { + Thread t = InnocuousThread.newSystemThread("Common-Cleaner", r); + t.setPriority(Thread.MAX_PRIORITY - 2); + return t; + } + }); + } } - }); + ); /** - * Cleaner for use within system modules. - * - * This Cleaner will run on a thread whose context class loader - * is {@code null}. The system cleaning action to perform in + * ExtendedCleaner for use within system modules. + *

+ * This Cleaner's cleaning loop will run on a thread whose context + * class loader is {@code null}. The system cleaning action to perform in * this Cleaner should handle a {@code null} context class loader. + *

+ * This Cleaner is an {@link ExtendedCleaner} which supports helping + * the cleaner thread from arbitrary application threads. The system cleaning + * action to perform in this Cleaner should also handle being executed by + * arbitrary application thread. * - * @return a Cleaner for use within system modules + * @return an ExtendedCleaner for use within system modules */ - public static Cleaner cleaner() { + public static ExtendedCleaner cleaner() { return commonCleaner; } } --- old/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-03-28 16:44:47.430064011 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java 2016-03-28 16:44:47.331065725 +0200 @@ -25,133 +25,230 @@ package jdk.internal.ref; +import jdk.internal.misc.InnocuousThread; +import jdk.internal.misc.JavaLangRefAccess; +import jdk.internal.misc.SharedSecrets; + 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; /** - * 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 operations to queue after + // 1st optimistic try fails so that only a single thread at a time is + // retrying operations while helping the Cleaner execute Cleanable(s) + // and trigger new Reference discovery before finally giving up. + private final StampedLock helpingLock = new StampedLock(); + + public boolean retryWhileHelpingClean(BooleanSupplier retriableOperation) { + // 1st optimistic try - allow concurrent execution of operations + // until helping is necessary + long stamp = helpingLock.tryReadLock(); + if (stamp != 0) try { + if (retriableOperation.getAsBoolean()) { + return true; + } + } finally { + helpingLock.unlockRead(stamp); + } - // The ReferenceQueue of pending cleaning actions - final ReferenceQueue queue; + // retrials with helping is exclusive + stamp = helpingLock.writeLock(); + try { + // retry operation while executing enqueued Cleanable(s) until the + // queue drains out + do { + if (retriableOperation.getAsBoolean()) { + return true; + } + } while (task.cleanNextEnqueued()); + + 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 new pending Cleanable(s) + // so retry operation while executing enqueued Cleanable(s) until the + // queue drains out + do { + if (retriableOperation.getAsBoolean()) { + return true; + } + } while (task.cleanNextEnqueued()); + + // give up finally + return false; + } 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. + */ + 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; } } } --- old/src/java.base/share/classes/jdk/internal/ref/PhantomCleanable.java 2016-03-28 16:44:47.669059873 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/PhantomCleanable.java 2016-03-28 16:44:47.580061414 +0200 @@ -63,8 +63,8 @@ * @param cleaner the {@code Cleaner} to register with */ public PhantomCleanable(T referent, Cleaner cleaner) { - super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue); - this.list = CleanerImpl.getCleanerImpl(cleaner).phantomCleanableList; + super(Objects.requireNonNull(referent), ((CleanerImpl)cleaner).queue()); + this.list = ((CleanerImpl)cleaner).phantomCleanableList(); insert(); // Ensure referent and cleaner remain accessible --- old/src/java.base/share/classes/jdk/internal/ref/SoftCleanable.java 2016-03-28 16:44:47.908055735 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/SoftCleanable.java 2016-03-28 16:44:47.820057259 +0200 @@ -63,8 +63,8 @@ * @param cleaner the {@code Cleaner} to register with */ public SoftCleanable(T referent, Cleaner cleaner) { - super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue); - list = CleanerImpl.getCleanerImpl(cleaner).softCleanableList; + super(Objects.requireNonNull(referent), ((CleanerImpl)cleaner).queue()); + list = ((CleanerImpl)cleaner).softCleanableList(); insert(); // Ensure referent and cleaner remain accessible --- old/src/java.base/share/classes/jdk/internal/ref/WeakCleanable.java 2016-03-28 16:44:48.144051649 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/WeakCleanable.java 2016-03-28 16:44:48.041053432 +0200 @@ -63,8 +63,8 @@ * @param cleaner the {@code Cleaner} to register new reference with */ public WeakCleanable(T referent, Cleaner cleaner) { - super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue); - list = CleanerImpl.getCleanerImpl(cleaner).weakCleanableList; + super(Objects.requireNonNull(referent), ((CleanerImpl)cleaner).queue()); + list = ((CleanerImpl)cleaner).weakCleanableList(); insert(); // Ensure referent and cleaner remain accessible --- old/src/java.base/share/classes/sun/nio/ch/DirectBuffer.java 2016-03-28 16:44:48.389047407 +0200 +++ new/src/java.base/share/classes/sun/nio/ch/DirectBuffer.java 2016-03-28 16:44:48.294049052 +0200 @@ -25,8 +25,8 @@ package sun.nio.ch; -import jdk.internal.ref.Cleaner; +import java.lang.ref.Cleaner; public interface DirectBuffer { @@ -34,6 +34,6 @@ public Object attachment(); - public Cleaner cleaner(); + public Cleaner.Cleanable cleaner(); } --- old/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-03-28 16:44:48.626043304 +0200 +++ new/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-03-28 16:44:48.530044966 +0200 @@ -27,6 +27,7 @@ import java.io.FileDescriptor; import java.io.IOException; +import java.lang.ref.Cleaner; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.ClosedByInterruptException; @@ -47,7 +48,6 @@ import jdk.internal.misc.JavaIOFileDescriptorAccess; import jdk.internal.misc.JavaNioAccess; import jdk.internal.misc.SharedSecrets; -import jdk.internal.ref.Cleaner; import sun.security.action.GetPropertyAction; public class FileChannelImpl @@ -845,7 +845,7 @@ } private static void unmap(MappedByteBuffer bb) { - Cleaner cl = ((DirectBuffer)bb).cleaner(); + Cleaner.Cleanable cl = ((DirectBuffer)bb).cleaner(); if (cl != null) cl.clean(); } --- /dev/null 2016-03-28 14:07:52.007986692 +0200 +++ new/src/java.base/share/classes/jdk/internal/ref/ExtendedCleaner.java 2016-03-28 16:44:48.776040707 +0200 @@ -0,0 +1,42 @@ +package jdk.internal.ref; + +import java.lang.ref.Cleaner; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; +import java.util.function.BooleanSupplier; + +/** + * An extension of public {@link Cleaner} with operations that can only be + * invoked from within {@code java.base} module or selected modules to + * which {@code jdk.internal.ref} package is exported. + */ +public interface ExtendedCleaner extends Cleaner { + + /** + * @return new ExtendedCleaner with default ThreadFactory. + */ + static ExtendedCleaner create() { + return new CleanerImpl.ExtendedImpl(null); + } + + /** + * @return new ExtendedCleaner with given ThreadFactory. + */ + static ExtendedCleaner create(ThreadFactory threadFactory) { + Objects.requireNonNull(threadFactory, "threadFactory"); + return new CleanerImpl.ExtendedImpl(threadFactory); + } + + /** + * Retries given {@code retriableOperation} while helping execute cleaning + * functions of pending Cleanable(s) registered by this ExtendedCleaner + * until the operation returns {@code true} or there are no more pending + * Cleanable(s) to clean. + * + * @param retriableOperation the operation to retry while helping with cleanup. + * @return {@code true} if the operation succeeded by returning {@code true} or + * {@code false} when the operation did not succeed before all pending + * Cleanable(s) have been executed. + */ + boolean retryWhileHelpingClean(BooleanSupplier retriableOperation); +} --- /dev/null 2016-03-28 14:07:52.007986692 +0200 +++ new/test/java/nio/Buffer/DirectBufferAllocOOMETest.java 2016-03-28 16:44:49.012036621 +0200 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * @test + * @bug 8149925 + * @summary Test that DirectByteBuffer allocation throws OOME and doesn't block + * indefinitely when MaxDirectMemorySize is exhausted + * + * @run main/othervm -XX:MaxDirectMemorySize=128m DirectBufferAllocOOMETest + */ +public class DirectBufferAllocOOMETest { + static final int CAPACITY = 1024 * 1024; // bytes + static volatile boolean wahr = true; + + static void allocUntilOOME() throws OutOfMemoryError { + List dbbs = new ArrayList<>(); + while (wahr) { + dbbs.add(ByteBuffer.allocateDirect(CAPACITY)); + } + Reference.reachabilityFence(dbbs); + } + + public static void main(String[] args) { + try { + allocUntilOOME(); + throw new IllegalStateException("Unexpected code path"); + } catch (OutOfMemoryError x) { + // expected + } + + // should succeed now + ByteBuffer.allocateDirect(CAPACITY); + + try { + allocUntilOOME(); + throw new IllegalStateException("Unexpected code path"); + } catch (OutOfMemoryError x) { + // expected + } + + // should succeed now + ByteBuffer.allocateDirect(CAPACITY); + } +} --- old/src/java.base/share/classes/jdk/internal/ref/Cleaner.java 2016-03-28 16:44:49.355030682 +0200 +++ /dev/null 2016-03-28 14:07:52.007986692 +0200 @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.internal.ref; - -import java.lang.ref.*; -import java.security.AccessController; -import java.security.PrivilegedAction; - - -/** - * General-purpose phantom-reference-based cleaners. - * - *

Cleaners are a lightweight and more robust alternative to finalization. - * They are lightweight because they are not created by the VM and thus do not - * require a JNI upcall to be created, and because their cleanup code is - * invoked directly by the reference-handler thread rather than by the - * finalizer thread. They are more robust because they use phantom references, - * the weakest type of reference object, thereby avoiding the nasty ordering - * problems inherent to finalization. - * - *

A cleaner tracks a referent object and encapsulates a thunk of arbitrary - * cleanup code. Some time after the GC detects that a cleaner's referent has - * become phantom-reachable, the reference-handler thread will run the cleaner. - * Cleaners may also be invoked directly; they are thread safe and ensure that - * they run their thunks at most once. - * - *

Cleaners are not a replacement for finalization. They should be used - * only when the cleanup code is extremely simple and straightforward. - * Nontrivial cleaners are inadvisable since they risk blocking the - * reference-handler thread and delaying further cleanup and finalization. - * - * - * @author Mark Reinhold - */ - -public class Cleaner - extends PhantomReference - implements Runnable -{ - - // Dummy reference queue, needed because the PhantomReference constructor - // insists that we pass a queue. Nothing will ever be placed on this queue - // since the reference handler invokes cleaners explicitly. - // - private static final ReferenceQueue dummyQueue = new ReferenceQueue<>(); - - // Doubly-linked list of live cleaners, which prevents the cleaners - // themselves from being GC'd before their referents - // - private static Cleaner first = null; - - private Cleaner - next = null, - prev = null; - - private static synchronized Cleaner add(Cleaner cl) { - if (first != null) { - cl.next = first; - first.prev = cl; - } - first = cl; - return cl; - } - - private static synchronized boolean remove(Cleaner cl) { - - // If already removed, do nothing - if (cl.next == cl) - return false; - - // Update list - if (first == cl) { - if (cl.next != null) - first = cl.next; - else - first = cl.prev; - } - if (cl.next != null) - cl.next.prev = cl.prev; - if (cl.prev != null) - cl.prev.next = cl.next; - - // Indicate removal by pointing the cleaner to itself - cl.next = cl; - cl.prev = cl; - return true; - - } - - private final Runnable thunk; - - private Cleaner(Object referent, Runnable thunk) { - super(referent, dummyQueue); - this.thunk = thunk; - } - - /** - * Creates a new cleaner. - * - * @param ob the referent object to be cleaned - * @param thunk - * The cleanup code to be run when the cleaner is invoked. The - * cleanup code is run directly from the reference-handler thread, - * so it should be as simple and straightforward as possible. - * - * @return The new cleaner - */ - public static Cleaner create(Object ob, Runnable thunk) { - if (thunk == null) - return null; - return add(new Cleaner(ob, thunk)); - } - - /** - * Runs this cleaner, if it has not been run before. - */ - public void clean() { - if (!remove(this)) - return; - try { - thunk.run(); - } catch (final Throwable x) { - AccessController.doPrivileged(new PrivilegedAction<>() { - public Void run() { - if (System.err != null) - new Error("Cleaner terminated abnormally", x) - .printStackTrace(); - System.exit(1); - return null; - }}); - } - } - - @Override public void run() { - SecurityManager security = System.getSecurityManager(); - if (security != null) - security.checkPackageAccess("jdk.internal.ref"); - this.clean(); - } - -} --- old/test/jdk/internal/ref/Cleaner/ExitOnThrow.java 2016-03-28 16:44:49.518027860 +0200 +++ /dev/null 2016-03-28 14:07:52.007986692 +0200 @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 4954921 8009259 - * @library /test/lib/share/classes - * @modules java.base/jdk.internal.ref - * @build jdk.test.lib.* - * @build jdk.test.lib.process.* - * @run main ExitOnThrow - * @summary Ensure that if a cleaner throws an exception then the VM exits - */ -import java.util.Arrays; - -import jdk.internal.ref.Cleaner; -import jdk.test.lib.JDKToolLauncher; -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; - -public class ExitOnThrow { - - static final String cp = System.getProperty("test.classes", "."); - - public static void main(String[] args) throws Exception { - if (args.length == 0) { - String[] cmd = JDKToolLauncher.createUsingTestJDK("java") - .addToolArg("-cp") - .addToolArg(cp) - .addToolArg("ExitOnThrow") - .addToolArg("-executeCleaner") - .getCommand(); - ProcessBuilder pb = new ProcessBuilder(cmd); - OutputAnalyzer out = ProcessTools.executeProcess(pb); - System.out.println("======================"); - System.out.println(Arrays.toString(cmd)); - String msg = " stdout: [" + out.getStdout() + "]\n" + - " stderr: [" + out.getStderr() + "]\n" + - " exitValue = " + out.getExitValue() + "\n"; - System.out.println(msg); - - if (out.getExitValue() != 1) - throw new RuntimeException("Unexpected exit code: " + - out.getExitValue()); - - } else { - Cleaner.create(new Object(), - () -> { throw new RuntimeException("Foo!"); } ); - while (true) { - System.gc(); - Thread.sleep(100); - } - } - } - -}