--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/ref/Cleaner.java Mon Dec 21 11:34:14 2015 -0500
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2015, 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 java.lang.ref;
+
+import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
+
+import jdk.internal.misc.CleanerImpl;
+
+/**
+ * {@code Cleaner} manages a set of object references and corresponding cleaning actions.
+ * <p>
+ * Cleaning actions are {@link #register(Object object, Runnable action) registered}
+ * to run after the cleaner is notified that the object has become
+ * phantom reachable.
+ * The cleaner uses {@link PhantomReference} and {@link ReferenceQueue} to be
+ * notified when the <a href="package-summary.html#reachability">reachability</a>
+ * changes.
+ * <p>
+ * Each cleaner operates independently, managing the pending cleaning actions
+ * and handling threading and termination when the cleaner is no longer in use.
+ * Registering an object reference and corresponding cleaning action returns
+ * a {@link Cleanable Cleanable}. The most efficient use is to explicitly invoke
+ * the {@link Cleanable#clean clean} method when the object is closed or
+ * no longer needed.
+ * The cleaning action is a {@link Runnable} to be invoked at most once when
+ * the object has become phantom reachable unless it has already been explicitly cleaned.
+ * Note that the cleaning action must not refer to the object being registered.
+ * If so, the object will not become phantom reachable and the cleaning action
+ * will not be invoked automatically.
+ * <p>
+ * The execution of the cleaning action is performed
+ * by a thread associated with the cleaner.
+ * All exceptions thrown by the cleaning action are ignored.
+ * The cleaner and other cleaning actions are not affected by
+ * exceptions in a cleaning action.
+ * The thread runs until all registered cleaning actions have
+ * completed and the cleaner itself is reclaimed by the garbage collector.
+ * <p>
+ * The behavior of cleaners during {@link System#exit(int) System.exit}
+ * is implementation specific. No guarantees are made relating
+ * to whether cleaning actions are invoked or not.
+ * <p>
+ * Unless otherwise noted, passing a {@code null} argument to a constructor or
+ * method in this class will cause a
+ * {@link java.lang.NullPointerException NullPointerException} to be thrown.
+ *
+ * @apiNote
+ * The cleaning action is invoked only after the associated object becomes
+ * phantom reachable, so it is important that the object implementing the
+ * cleaning action does not hold references to the object.
+ * In this example, a static class encapsulates the cleaning state and action.
+ * An "inner" class, anonymous or not, must not be used because it implicitly
+ * contains a reference to the outer instance, preventing it from becoming
+ * phantom reachable.
+ * The choice of a new cleaner or sharing an existing cleaner is determined
+ * by the use case.
+ * <p>
+ * If the CleaningExample is used in a try-finally block then the
+ * {@code close} method calls the cleaning action.
+ * If the {@code close} method is not called, the cleaning action is called
+ * by the Cleaner when the CleaningExample instance has become phantom reachable.
+ * <pre>{@code
+ * public class CleaningExample implements AutoCloseable {
+ * // A cleaner, preferably one shared within a library
+ * private static final Cleaner cleaner = <cleaner>;
+ *
+ * static class State implements Runnable {
+ *
+ * State(...) {
+ * // initialize State needed for cleaning action
+ * }
+ *
+ * public void run() {
+ * // cleanup action accessing State, executed at most once
+ * }
+ * }
+ *
+ * private final State;
+ * private final Cleaner.Cleanable cleanable
+ *
+ * public CleaningExample() {
+ * this.state = new State(...);
+ * this.cleanable = cleaner.register(this, state);
+ * }
+ *
+ * public void close() {
+ * cleanable.clean();
+ * }
+ * }
+ * }</pre>
+ * The cleaning action could be a lambda but all too easily will capture
+ * the object reference, by referring to fields of the object being cleaned,
+ * preventing the object from becoming phantom reachable.
+ * Using a static nested class, as above, will avoid accidentally retaining the
+ * object reference.
+ * <p>
+ * <a name="compatible-cleaners"></a>
+ * Cleaning actions should be prepared to be invoked concurrently with
+ * other cleaning actions.
+ * Typically the cleaning actions should be very quick to execute
+ * and not block. If the cleaning action blocks, it may delay processing
+ * other cleaning actions registered to the same cleaner.
+ * 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((Cleaner c) -> c.impl);
+ }
+
+ /**
+ * Construct a Cleaner implementation and start it.
+ */
+ private Cleaner() {
+ impl = new CleanerImpl();
+ }
+
+ /**
+ * Returns a new {@code Cleaner}.
+ * <p>
+ * The cleaner creates a {@link Thread#setDaemon(boolean) daemon thread}
+ * to process the phantom reachable objects and to invoke cleaning actions.
+ * The {@linkplain java.lang.Thread#getContextClassLoader context class loader}
+ * of the thread is set to the
+ * {@link ClassLoader#getSystemClassLoader() system class loader}.
+ * The thread has no permissions, enforced only if a
+ * {@link java.lang.System#setSecurityManager(SecurityManager) SecurityManager is set}.
+ * <p>
+ * The cleaner terminates when it is phantom reachable and all of the
+ * registered cleaning actions are complete.
+ *
+ * @return a new {@code Cleaner}
+ *
+ * @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;
+ }
+
+ /**
+ * Returns a new {@code Cleaner} using a {@code Thread} from the {@code ThreadFactory}.
+ * <p>
+ * A thread from the thread factory's {@link ThreadFactory#newThread(Runnable) newThread}
+ * method is set to be a {@link Thread#setDaemon(boolean) daemon thread}
+ * and started to process phantom reachable objects and invoke cleaning actions.
+ * On each call the {@link ThreadFactory#newThread(Runnable) thread factory}
+ * must provide a Thread that is suitable for performing the cleaning actions.
+ * <p>
+ * The cleaner terminates when it is phantom reachable and all of the
+ * registered cleaning actions are complete.
+ *
+ * @param threadFactory a {@code ThreadFactory} to return a new {@code Thread}
+ * to process cleaning actions
+ * @return a new {@code Cleaner}
+ *
+ * @throws IllegalThreadStateException if the thread from the thread
+ * factory was {@link Thread.State#NEW not a new thread}.
+ * @throws SecurityException if the current thread is not allowed to
+ * create or start the thread.
+ */
+ public static Cleaner create(ThreadFactory threadFactory) {
+ Objects.requireNonNull(threadFactory, "threadFactory");
+ Cleaner cleaner = new Cleaner();
+ cleaner.impl.start(cleaner, threadFactory);
+ return cleaner;
+ }
+
+ /**
+ * Registers an object and a cleaning action to run when the object
+ * becomes phantom reachable.
+ * Refer to the <a href="#compatible-cleaners">API Note</a> above for
+ * cautions about the behavior of cleaning actions.
+ *
+ * @param obj the object to monitor
+ * @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);
+ }
+
+ /**
+ * {@code Cleanable} represents an object and a
+ * cleaning action registered in a {@code Cleaner}.
+ * @since 9
+ */
+ public interface Cleanable {
+ /**
+ * Unregisters the cleanable and invokes the cleaning action.
+ * The cleanable's cleaning action is invoked at most once
+ * regardless of the number of calls to {@code clean}.
+ */
+ void clean();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/misc/CleanerImpl.java Mon Dec 21 11:34:14 2015 -0500
@@ -0,0 +1,790 @@
+/*
+ * Copyright (c) 2015, 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.misc;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Cleaner.Cleanable;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
+import java.util.function.Function;
+
+import sun.misc.InnocuousThread;
+import sun.misc.ManagedLocalsThread;
+
+/**
+ * CleanerImpl manages a set of object references and corresponding cleaning actions.
+ * CleanerImpl provides the functionality of {@link java.lang.ref.Cleaner}.
+ */
+public final class CleanerImpl implements Runnable {
+
+ /**
+ * An object to access the CleanerImpl from a Cleaner; set by Cleaner init.
+ */
+ private static Function<Cleaner, CleanerImpl> cleanerImplAccess = null;
+
+ /**
+ * 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<Object> queue;
+
+ /**
+ * 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<Cleaner, CleanerImpl> access) {
+ if (cleanerImplAccess == null) {
+ cleanerImplAccess = access;
+ }
+ }
+
+ /**
+ * Called to get the CleanerImpl for a Cleaner.
+ * @param cleaner the cleaner
+ * @return the corresponding CleanerImpl
+ */
+ private static CleanerImpl getCleanerImpl(Cleaner cleaner) {
+ return cleanerImplAccess.apply(cleaner);
+ }
+
+ /**
+ * Constructor for CleanerImpl.
+ */
+ public CleanerImpl() {
+ queue = new ReferenceQueue<>();
+ phantomCleanableList = new PhantomCleanableRef(this);
+ weakCleanableList = new WeakCleanableRef(this);
+ softCleanableList = new SoftCleanableRef(this);
+ }
+
+ /**
+ * Starts the Cleaner implementation.
+ * When started waits for Cleanables to be queued.
+ * @param service the cleaner
+ * @param threadFactory the thread factory
+ */
+ public void start(Cleaner service, ThreadFactory threadFactory) {
+ // schedule a nop cleaning action for the service, so the associated thread
+ // will continue to run at least until the service is reclaimable.
+ new PhantomCleanableRef(service, service, () -> {});
+
+ if (threadFactory == null) {
+ threadFactory = CleanerImpl.InnocuousThreadFactory.factory();
+ }
+
+ // now that there's at least one cleaning action, for the service,
+ // 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.
+ * <p>
+ * If the thread is a ManagedLocalsThread, the threadlocals
+ * are erased before each cleanup
+ */
+ public void run() {
+ Thread t = Thread.currentThread();
+ ManagedLocalsThread mlThread = (t instanceof ManagedLocalsThread)
+ ? (ManagedLocalsThread) 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 (InterruptedException i) {
+ continue; // ignore the interruption
+ } catch (Throwable e) {
+ // ignore exceptions from the cleanup action
+ }
+ }
+ }
+
+ /**
+ * PhantomCleanable subclasses efficiently encapsulate cleanup state and
+ * the cleaning action.
+ * Subclasses implement the abstract {@link #performCleanup()} method
+ * to provide the cleaning action.
+ * When constructed, the object reference and the {@link Cleanable Cleanable}
+ * are registered with the {@link Cleaner}.
+ * The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
+ * referent becomes phantom reachable.
+ */
+ public static abstract class PhantomCleanable<T> extends PhantomReference<T>
+ implements Cleaner.Cleanable {
+
+ /**
+ * Links to previous and next in a doubly-linked list.
+ */
+ PhantomCleanable<?> prev = this, next = this;
+
+ /**
+ * The CleanerImpl for this Cleanable.
+ */
+ private final CleanerImpl cleanerImpl;
+
+ /**
+ * Constructs new {@code PhantomCleanable} with
+ * {@code non-null referent} and {@code non-null cleaner}.
+ * The {@code cleaner} is not retained; it is only used to
+ * register the newly constructed {@link Cleaner.Cleanable Cleanable}.
+ *
+ * @param referent the referent to track
+ * @param cleaner the {@code Cleaner} to register with
+ */
+ public PhantomCleanable(T referent, Cleaner cleaner) {
+ super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
+ this.cleanerImpl = getCleanerImpl(cleaner);
+ insert();
+
+ // TODO: Replace getClass() with ReachabilityFence when it is available
+ cleaner.getClass();
+ referent.getClass();
+ }
+
+ /**
+ * Construct a new root of the list; not inserted.
+ */
+ PhantomCleanable(CleanerImpl cleanerImpl) {
+ super(null, null);
+ this.cleanerImpl = cleanerImpl;
+ }
+
+ /**
+ * Insert this PhantomCleanable after the list head.
+ */
+ private void insert() {
+ final PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
+ synchronized (list) {
+ prev = list;
+ next = list.next;
+ next.prev = this;
+ list.next = this;
+ }
+ }
+
+ /**
+ * Remove this PhantomCleanable from the list.
+ *
+ * @return true if Cleanable was removed or false if not because
+ * it had already been removed before
+ */
+ private boolean remove() {
+ PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
+ synchronized (list) {
+ if (next != this) {
+ next.prev = prev;
+ prev.next = next;
+ prev = this;
+ next = this;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the list's next reference refers to itself.
+ *
+ * @return true if the list is empty
+ */
+ boolean isListEmpty() {
+ PhantomCleanable<?> list = cleanerImpl.phantomCleanableList;
+ synchronized (list) {
+ return list == list.next;
+ }
+ }
+
+ /**
+ * Unregister this PhantomCleanable and invoke {@link #performCleanup()},
+ * ensuring at-most-once semantics.
+ */
+ @Override
+ public final void clean() {
+ if (remove()) {
+ super.clear();
+ performCleanup();
+ }
+ }
+
+ /**
+ * Unregister this PhantomCleanable and clear the reference.
+ * Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
+ */
+ @Override
+ public void clear() {
+ if (remove()) {
+ super.clear();
+ }
+ }
+
+ /**
+ * The {@code performCleanup} abstract method is overridden
+ * to implement the cleaning logic.
+ * The {@code performCleanup} method should not be called except
+ * by the {@link #clean} method which ensures at most once semantics.
+ */
+ protected abstract void performCleanup();
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean isEnqueued() {
+ throw new UnsupportedOperationException("isEnqueued");
+ }
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean enqueue() {
+ throw new UnsupportedOperationException("enqueue");
+ }
+ }
+
+ /**
+ * WeakCleanable subclasses efficiently encapsulate cleanup state and
+ * the cleaning action.
+ * Subclasses implement the abstract {@link #performCleanup()} method
+ * to provide the cleaning action.
+ * When constructed, the object reference and the {@link Cleanable Cleanable}
+ * are registered with the {@link Cleaner}.
+ * The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
+ * referent becomes weakly reachable.
+ */
+ public static abstract class WeakCleanable<T> extends WeakReference<T>
+ implements Cleaner.Cleanable {
+
+ /**
+ * Links to previous and next in a doubly-linked list.
+ */
+ WeakCleanable<?> prev = this, next = this;
+
+ /**
+ * The CleanerImpl for this Cleanable.
+ */
+ private final CleanerImpl cleanerImpl;
+
+ /**
+ * Constructs new {@code WeakCleanableReference} with
+ * {@code non-null referent} and {@code non-null cleaner}.
+ * The {@code cleaner} is not retained by this reference; it is only used
+ * to register the newly constructed {@link Cleaner.Cleanable Cleanable}.
+ *
+ * @param referent the referent to track
+ * @param cleaner the {@code Cleaner} to register new reference with
+ */
+ public WeakCleanable(T referent, Cleaner cleaner) {
+ super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
+ cleanerImpl = getCleanerImpl(cleaner);
+ insert();
+
+ // TODO: Replace getClass() with ReachabilityFence when it is available
+ cleaner.getClass();
+ referent.getClass();
+ }
+
+ /**
+ * Construct a new root of the list; not inserted.
+ */
+ WeakCleanable(CleanerImpl cleanerImpl) {
+ super(null, null);
+ this.cleanerImpl = cleanerImpl;
+ }
+
+ /**
+ * Insert this WeakCleanableReference after the list head.
+ */
+ private void insert() {
+ final WeakCleanable<?> list = cleanerImpl.weakCleanableList;
+ synchronized (list) {
+ prev = list;
+ next = list.next;
+ next.prev = this;
+ list.next = this;
+ }
+ }
+
+ /**
+ * Remove this WeakCleanableReference from the list.
+ *
+ * @return true if Cleanable was removed or false if not because
+ * it had already been removed before
+ */
+ private boolean remove() {
+ WeakCleanable<?> list = cleanerImpl.weakCleanableList;
+ synchronized (list) {
+ if (next != this) {
+ next.prev = prev;
+ prev.next = next;
+ prev = this;
+ next = this;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the list's next reference refers to itself.
+ *
+ * @return true if the list is empty
+ */
+ boolean isListEmpty() {
+ WeakCleanable<?> list = cleanerImpl.weakCleanableList;
+ synchronized (list) {
+ return list == list.next;
+ }
+ }
+
+ /**
+ * Unregister this WeakCleanable reference and invoke {@link #performCleanup()},
+ * ensuring at-most-once semantics.
+ */
+ @Override
+ public final void clean() {
+ if (remove()) {
+ super.clear();
+ performCleanup();
+ }
+ }
+
+ /**
+ * Unregister this WeakCleanable and clear the reference.
+ * Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
+ */
+ @Override
+ public void clear() {
+ if (remove()) {
+ super.clear();
+ }
+ }
+
+ /**
+ * The {@code performCleanup} abstract method is overridden
+ * to implement the cleaning logic.
+ * The {@code performCleanup} method should not be called except
+ * by the {@link #clean} method which ensures at most once semantics.
+ */
+ protected abstract void performCleanup();
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link java.lang.ref.Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean isEnqueued() {
+ throw new UnsupportedOperationException("isEnqueued");
+ }
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link java.lang.ref.Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean enqueue() {
+ throw new UnsupportedOperationException("enqueue");
+ }
+ }
+
+ /**
+ * SoftCleanable subclasses efficiently encapsulate cleanup state and
+ * the cleaning action.
+ * Subclasses implement the abstract {@link #performCleanup()} method
+ * to provide the cleaning action.
+ * When constructed, the object reference and the {@link Cleanable Cleanable}
+ * are registered with the {@link Cleaner}.
+ * The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the
+ * referent becomes softly reachable.
+ */
+ public static abstract class SoftCleanable<T> extends SoftReference<T>
+ implements Cleaner.Cleanable {
+
+ /**
+ * Links to previous and next in a doubly-linked list.
+ */
+ SoftCleanable<?> prev = this, next = this;
+
+ /**
+ * The CleanerImpl for this Cleanable.
+ */
+ private final CleanerImpl cleanerImpl;
+
+ /**
+ * Constructs new {@code SoftCleanableReference} with
+ * {@code non-null referent} and {@code non-null cleaner}.
+ * The {@code cleaner} is not retained by this reference; it is only used
+ * to register the newly constructed {@link Cleaner.Cleanable Cleanable}.
+ *
+ * @param referent the referent to track
+ * @param cleaner the {@code Cleaner} to register with
+ */
+ public SoftCleanable(T referent, Cleaner cleaner) {
+ super(Objects.requireNonNull(referent), getCleanerImpl(cleaner).queue);
+ cleanerImpl = getCleanerImpl(cleaner);
+ insert();
+
+ // TODO: Replace getClass() with ReachabilityFence when it is available
+ cleaner.getClass();
+ referent.getClass();
+ }
+
+ /**
+ * Construct a new root of the list; not inserted.
+ */
+ SoftCleanable(CleanerImpl cleanerImpl) {
+ super(null, null);
+ this.cleanerImpl = cleanerImpl;
+ }
+
+ /**
+ * Insert this SoftCleanableReference after the list head.
+ */
+ private void insert() {
+ final SoftCleanable<?> list = cleanerImpl.softCleanableList;
+ synchronized (list) {
+ prev = list;
+ next = list.next;
+ next.prev = this;
+ list.next = this;
+ }
+ }
+
+ /**
+ * Remove this SoftCleanableReference from the list.
+ *
+ * @return true if Cleanable was removed or false if not because
+ * it had already been removed before
+ */
+ private boolean remove() {
+ SoftCleanable<?> list = cleanerImpl.softCleanableList;
+ synchronized (list) {
+ if (next != this) {
+ next.prev = prev;
+ prev.next = next;
+ prev = this;
+ next = this;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the list's next reference refers to itself.
+ *
+ * @return true if the list is empty
+ */
+ boolean isListEmpty() {
+ SoftCleanable<?> list = cleanerImpl.softCleanableList;
+ synchronized (list) {
+ return list == list.next;
+ }
+ }
+
+ /**
+ * Unregister this SoftCleanable reference and invoke {@link #performCleanup()},
+ * ensuring at-most-once semantics.
+ */
+ @Override
+ public final void clean() {
+ if (remove()) {
+ super.clear();
+ performCleanup();
+ }
+ }
+
+ /**
+ * Unregister this SoftCleanable and clear the reference.
+ * Due to inherent concurrency, {@link #performCleanup()} may still be invoked.
+ */
+ @Override
+ public void clear() {
+ if (remove()) {
+ super.clear();
+ }
+ }
+
+ /**
+ * The {@code performCleanup} abstract method is overridden
+ * to implement the cleaning logic.
+ * The {@code performCleanup} method should not be called except
+ * by the {@link #clean} method which ensures at most once semantics.
+ */
+ protected abstract void performCleanup();
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean isEnqueued() {
+ throw new UnsupportedOperationException("isEnqueued");
+ }
+
+ /**
+ * This method always throws {@link UnsupportedOperationException}.
+ * Enqueuing details of {@link Cleaner.Cleanable}
+ * are a private implementation detail.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public final boolean enqueue() {
+ throw new UnsupportedOperationException("enqueue");
+ }
+ }
+
+ /**
+ * Perform cleaning on an unreachable PhantomReference.
+ */
+ public static final class PhantomCleanableRef extends PhantomCleanable<Object> {
+ private final Runnable action;
+
+ /**
+ * Constructor for a phantom cleanable reference.
+ * @param obj the object to monitor
+ * @param cleaner the cleaner
+ * @param action the action Runnable
+ */
+ public PhantomCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
+ super(obj, cleaner);
+ this.action = action;
+ }
+
+ /**
+ * Constructor used only for root of phantom cleanable list.
+ * @param cleanerImpl the cleanerImpl
+ */
+ PhantomCleanableRef(CleanerImpl cleanerImpl) {
+ super(cleanerImpl);
+ this.action = null;
+ }
+
+ @Override
+ protected void performCleanup() {
+ action.run();
+ }
+
+ /**
+ * Prevent access to referent even when it is still alive.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public Object get() {
+ throw new UnsupportedOperationException("get");
+ }
+
+ /**
+ * Direct clearing of the referent is not supported.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("clear");
+ }
+ }
+
+ /**
+ * Perform cleaning on an unreachable WeakReference.
+ */
+ public static final class WeakCleanableRef extends WeakCleanable<Object> {
+ private final Runnable action;
+
+ /**
+ * Constructor for a weak cleanable reference.
+ * @param obj the object to monitor
+ * @param cleaner the cleaner
+ * @param action the action Runnable
+ */
+ WeakCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
+ super(obj, cleaner);
+ this.action = action;
+ }
+
+ /**
+ * Constructor used only for root of weak cleanable list.
+ * @param cleanerImpl the cleanerImpl
+ */
+ WeakCleanableRef(CleanerImpl cleanerImpl) {
+ super(cleanerImpl);
+ this.action = null;
+ }
+
+ @Override
+ protected void performCleanup() {
+ action.run();
+ }
+
+ /**
+ * Prevent access to referent even when it is still alive.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public Object get() {
+ throw new UnsupportedOperationException("get");
+ }
+
+ /**
+ * Direct clearing of the referent is not supported.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("clear");
+ }
+ }
+
+ /**
+ * Perform cleaning on an unreachable SoftReference.
+ */
+ public static final class SoftCleanableRef extends SoftCleanable<Object> {
+ private final Runnable action;
+
+ /**
+ * Constructor for a soft cleanable reference.
+ * @param obj the object to monitor
+ * @param cleaner the cleaner
+ * @param action the action Runnable
+ */
+ SoftCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
+ super(obj, cleaner);
+ this.action = action;
+ }
+
+ /**
+ * Constructor used only for root of soft cleanable list.
+ * @param cleanerImpl the cleanerImpl
+ */
+ SoftCleanableRef(CleanerImpl cleanerImpl) {
+ super(cleanerImpl);
+ this.action = null;
+ }
+
+ @Override
+ protected void performCleanup() {
+ action.run();
+ }
+
+ /**
+ * Prevent access to referent even when it is still alive.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public Object get() {
+ throw new UnsupportedOperationException("get");
+ }
+
+ /**
+ * Direct clearing of the referent is not supported.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("clear");
+ }
+
+ }
+
+ /**
+ * A ThreadFactory for InnocuousThreads.
+ * The factory is a singleton.
+ */
+ static final class InnocuousThreadFactory implements ThreadFactory {
+ final static ThreadFactory factory = new InnocuousThreadFactory();
+
+ static ThreadFactory factory() {
+ return factory;
+ }
+
+ public Thread newThread(Runnable r) {
+ return AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
+ Thread t = new InnocuousThread(r);
+ t.setPriority(Thread.MAX_PRIORITY - 2);
+ t.setName("Cleaner-" + t.getId());
+ return t;
+ });
+ }
+ }
+
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ref/CleanerTest.java Mon Dec 21 11:34:14 2015 -0500
@@ -0,0 +1,735 @@
+/*
+ * Copyright (c) 2015, 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.Cleaner;
+import java.lang.ref.Reference;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import jdk.internal.misc.CleanerImpl.PhantomCleanable;
+import jdk.internal.misc.CleanerImpl.WeakCleanable;
+import jdk.internal.misc.CleanerImpl.SoftCleanable;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @library /lib/testlibrary
+ * @modules java.base/jdk.internal.misc
+ * @run testng/othervm -Xmx4m CleanerTest
+ */
+
+@Test
+public class CleanerTest {
+ // A common CleaningService used by the test for notifications
+ static final Cleaner COMMON = Cleaner.create();
+
+ /**
+ * Test that sequences of the various actions on a Reference
+ * and on the Cleanable instance have the desired result.
+ * The test cases are generated for each of phantom, weak and soft
+ * references.
+ * The sequence of actions includes all permutations to an initial
+ * list of actions including clearing the ref and resulting garbage
+ * collection actions on the reference and explicitly performing
+ * the cleaning action.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ void testCleanableActions() {
+ Cleaner cleaner = Cleaner.create();
+
+ // Individually
+ generateCases(cleaner, c -> c.clearRef());
+ generateCases(cleaner, c -> c.doClean());
+
+ // Pairs
+ generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
+
+ CleanableCase s = setupPhantom(COMMON, cleaner);
+ cleaner = null;
+ Assert.assertTrue(checkCleaned(s.getSemaphore()),
+ "Cleaner cleanup should have occurred");
+ }
+
+ /**
+ * Test the jdk.internal.misc APIs with sequences of the various actions
+ * on a Reference and on the Cleanable instance have the desired result.
+ * The test cases are generated for each of phantom, weak and soft
+ * references.
+ * The sequence of actions includes all permutations to an initial
+ * list of actions including clearing the ref and resulting garbage
+ * collection actions on the reference, explicitly performing
+ * the cleanup and explicitly clearing the cleaning action.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ void testRefSubtypes() {
+ Cleaner cleaner = Cleaner.create();
+
+ // Individually
+ generateCasesInternal(cleaner, c -> c.clearRef());
+ generateCasesInternal(cleaner, c -> c.doClean());
+ generateCasesInternal(cleaner, c -> c.doClear());
+
+ // Pairs
+ generateCasesInternal(cleaner,
+ c -> c.doClear(), c -> c.doClean());
+
+ // Triplets
+ generateCasesInternal(cleaner,
+ c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
+
+ generateExceptionCasesInternal(cleaner);
+
+ CleanableCase s = setupPhantom(COMMON, cleaner);
+ cleaner = null;
+ Assert.assertTrue(checkCleaned(s.getSemaphore()),
+ "Cleaner cleanup should have occurred");
+ }
+
+ /**
+ * Generate tests using the runnables for each of phantom, weak,
+ * and soft references.
+ * @param cleaner the cleaner
+ * @param runnables the sequence of actions on the test case
+ */
+ @SuppressWarnings("unchecked")
+ void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
+ generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables);
+ }
+
+ @SuppressWarnings("unchecked")
+ void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
+ generateCases(() -> setupPhantomSubclass(cleaner, null),
+ runnables.length, runnables);
+ generateCases(() -> setupWeakSubclass(cleaner, null),
+ runnables.length, runnables);
+ generateCases(() -> setupSoftSubclass(cleaner, null),
+ runnables.length, runnables);
+ }
+
+ @SuppressWarnings("unchecked")
+ void generateExceptionCasesInternal(Cleaner cleaner) {
+ generateCases(() -> setupPhantomSubclassException(cleaner, null),
+ 1, c -> c.clearRef());
+ generateCases(() -> setupWeakSubclassException(cleaner, null),
+ 1, c -> c.clearRef());
+ generateCases(() -> setupSoftSubclassException(cleaner, null),
+ 1, c -> c.clearRef());
+ }
+
+ /**
+ * Generate all permutations of the sequence of runnables
+ * and test each one.
+ * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges.
+ * @param generator the supplier of a CleanableCase
+ * @param n the first index to interchange
+ * @param runnables the sequence of actions
+ */
+ @SuppressWarnings("unchecked")
+ void generateCases(Supplier<CleanableCase> generator, int n,
+ Consumer<CleanableCase> ... runnables) {
+ if (n == 1) {
+ CleanableCase test = generator.get();
+ try {
+ verifyGetRef(test);
+
+ // Apply the sequence of actions on the Ref
+ for (Consumer<CleanableCase> c : runnables) {
+ c.accept(test);
+ }
+ verify(test);
+ } catch (Exception e) {
+ Assert.fail(test.toString(), e);
+ }
+ } else {
+ for (int i = 0; i < n - 1; i += 1) {
+ generateCases(generator, n - 1, runnables);
+ Consumer<CleanableCase> t = runnables[n - 1];
+ int ndx = ((n & 1) == 0) ? i : 0;
+ runnables[n - 1] = runnables[ndx];
+ runnables[ndx] = t;
+ }
+ generateCases(generator, n - 1, runnables);
+ }
+ }
+
+ /**
+ * Verify the test case.
+ * Any actions directly on the Reference or Cleanable have been executed.
+ * The CleanableCase under test is given a chance to do the cleanup
+ * by forcing a GC.
+ * The result is compared with the expected result computed
+ * from the sequence of operations on the Cleanable.
+ * The Cleanable itself should have been cleanedup.
+ *
+ * @param test A CleanableCase containing the references
+ */
+ void verify(CleanableCase test) {
+ System.out.println(test);
+ int r = test.expectedResult();
+
+ CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
+ test.clearCleanable(); // release this hard reference
+
+ boolean result = checkCleaned(test.getSemaphore());
+ if (result) {
+ Assert.assertEquals(r, CleanableCase.EV_CLEAN,
+ "cleaned; but not expected");
+ } else {
+ Assert.assertNotEquals(r, CleanableCase.EV_CLEAN,
+ "not cleaned; expected cleaning");
+ }
+ Assert.assertTrue(checkCleaned(cc.getSemaphore()),
+ "The reference to the Cleanable should have been freed");
+ }
+
+ /**
+ * Verify that the reference.get works (or not) as expected.
+ * It handles the cases where UnsupportedOperationException is expected.
+ *
+ * @param test the CleanableCase
+ */
+ void verifyGetRef(CleanableCase test) {
+ Reference<?> r = (Reference) test.getCleanable();
+ try {
+ Object o = r.get();
+ Reference<?> expectedRef = test.getRef();
+ Assert.assertEquals(expectedRef.get(), o,
+ "Object reference incorrect");
+ if (r.getClass().getName().endsWith("CleanableRef")) {
+ Assert.fail("should not be able to get referent");
+ }
+ } catch (UnsupportedOperationException uoe) {
+ if (r.getClass().getName().endsWith("CleanableRef")) {
+ // Expected exception
+ } else {
+ Assert.fail("Unexpected exception from subclassed cleanable: " +
+ uoe.getMessage() + ", class: " + r.getClass());
+ }
+ }
+ }
+
+ /**
+ * Test that releasing the reference to the Cleaner service allows it to be
+ * be freed.
+ */
+ @Test
+ void testCleanerTermination() {
+ ReferenceQueue<Object> queue = new ReferenceQueue<>();
+ Cleaner service = Cleaner.create();
+
+ PhantomReference<Object> ref = new PhantomReference<>(service, queue);
+ System.gc();
+ // Clear the Reference to the cleaning service and force a gc.
+ service = null;
+ System.gc();
+ try {
+ Reference<?> r = queue.remove(1000L);
+ Assert.assertNotNull(r, "queue.remove timeout,");
+ Assert.assertEquals(r, ref, "Wrong Reference dequeued");
+ } catch (InterruptedException ie) {
+ System.out.printf("queue.remove Interrupted%n");
+ }
+ }
+
+ /**
+ * Check a set of semaphores having been released by cleanup handlers.
+ * Force a number of GC cycles to give the GC a chance to process
+ * all the References and for the cleanup actions to be run.
+ *
+ * @param semaphore a varargs list of Semaphores
+ * @return true if all of the semaphores have at least 1 permit,
+ * false otherwise.
+ */
+ static boolean checkCleaned(Semaphore... semaphore) {
+ long[] cycles = new long[semaphore.length];
+ long total = 0;
+ for (int cycle = 0; cycle < 20; cycle++) {
+ for (int i = 0; i < semaphore.length; i++) {
+ long count = semaphore[i].availablePermits();
+ if (count > 0 && cycles[i] == 0) {
+ System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycle);
+ cycles[i] = cycle;
+ total += 1;
+ }
+ }
+
+ if (total == semaphore.length) {
+ System.out.printf(" All cleanups done in cycle: %d, total: %d%n",
+ cycle, total);
+ for (int i = 0; i < semaphore.length; i++) {
+ long count = semaphore[i].availablePermits();
+ Assert.assertEquals(count, 1,
+ "Cleanable invoked more than once, semaphore " + i);
+ }
+ return true; // all references freed
+ }
+ // Force GC
+ memoryPressure();
+ }
+ // Not all objects have been cleaned
+
+ for (int i = 0; i < semaphore.length; i++) {
+ if (cycles[i] != 0) {
+ System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycles[i]);
+ } else {
+ System.out.printf(" Cleanable[%d] not cleaned%n", i);
+ }
+ }
+
+ return false; // Failing result
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantom(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+ Cleaner.Cleanable c1 = cleaner.register(obj, () -> s1.release());
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ }
+ };
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+ }
+ /**
+ * Create a CleanableCase for a WeakReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupWeakSubclass(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ }
+ };
+
+ return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
+ }
+
+ /**
+ * Create a CleanableCase for a SoftReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupSoftSubclass(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ }
+ };
+
+ return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
+ }
+
+ /**
+ * Create a CleanableCase for a PhantomReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ throw new RuntimeException("Exception thrown to cleaner thread");
+ }
+ };
+
+ return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
+ }
+
+ /**
+ * Create a CleanableCase for a WeakReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupWeakSubclassException(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ throw new RuntimeException("Exception thrown to cleaner thread");
+ }
+ };
+
+ return new CleanableCase(new WeakReference<>(obj, null), c1, s1, true);
+ }
+
+ /**
+ * Create a CleanableCase for a SoftReference.
+ * @param cleaner the cleaner to use
+ * @param obj an object or null to create a new Object
+ * @return a new CleanableCase preset with the object, cleanup, and semaphore
+ */
+ static CleanableCase setupSoftSubclassException(Cleaner cleaner, Object obj) {
+ if (obj == null) {
+ obj = new Object();
+ }
+ Semaphore s1 = new Semaphore(0);
+
+ Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
+ protected void performCleanup() {
+ s1.release();
+ throw new RuntimeException("Exception thrown to cleaner thread");
+ }
+ };
+
+ return new CleanableCase(new SoftReference<>(obj, null), c1, s1, true);
+ }
+
+ /**
+ * MemoryPressure allocates memory to force a gc and to clear SoftReferences.
+ */
+ static void memoryPressure() {
+ SoftReference<Object> soft = new SoftReference<>(new Object(), null);
+ Vector<Object> root = new Vector<>();
+ try {
+ long free = 0;
+ while (soft.get() != null) {
+ long[] extra = new long[50_000];
+ root.addElement(extra);
+ }
+ } catch (OutOfMemoryError mem) {
+ // ignore
+ root = null;
+ }
+ }
+
+ /**
+ * CleanableCase encapsulates the objects used for a test.
+ * The reference to the object is not held directly,
+ * but in a Reference object that can be cleared.
+ * The semaphore is used to count whether the cleanup occurred.
+ * It can be awaited on to determine that the cleanup has occurred.
+ * It can be checked for non-zero to determine if it was
+ * invoked or if it was invoked twice (a bug).
+ */
+ static class CleanableCase {
+
+ private volatile Reference<?> ref;
+ private volatile Cleaner.Cleanable cleanup;
+ private final Semaphore semaphore;
+ private final boolean throwsEx;
+ private final int[] events; // Sequence of calls to clean, clear, etc.
+ private volatile int eventNdx;
+
+ public static int EV_UNKNOWN = 0;
+ public static int EV_CLEAR = 1;
+ public static int EV_CLEAN = 2;
+ public static int EV_UNREF = 3;
+ public static int EV_CLEAR_CLEANUP = 4;
+
+
+ CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ Semaphore semaphore) {
+ this.ref = ref;
+ this.cleanup = cleanup;
+ this.semaphore = semaphore;
+ this.throwsEx = false;
+ this.events = new int[4];
+ this.eventNdx = 0;
+ }
+ CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+ Semaphore semaphore,
+ boolean throwsEx) {
+ this.ref = ref;
+ this.cleanup = cleanup;
+ this.semaphore = semaphore;
+ this.throwsEx = throwsEx;
+ this.events = new int[4];
+ this.eventNdx = 0;
+ }
+
+ public Reference<?> getRef() {
+ return ref;
+ }
+
+ public void clearRef() {
+ addEvent(EV_UNREF);
+ ref.clear();
+ }
+
+ public Cleaner.Cleanable getCleanable() {
+ return cleanup;
+ }
+
+ public void doClean() {
+ try {
+ addEvent(EV_CLEAN);
+ cleanup.clean();
+ } catch (RuntimeException ex) {
+ if (!throwsEx) {
+ // unless it is known this case throws an exception, rethrow
+ throw ex;
+ }
+ }
+ }
+
+ public void doClear() {
+ addEvent(EV_CLEAR);
+ ((Reference)cleanup).clear();
+ }
+
+ public void clearCleanable() {
+ addEvent(EV_CLEAR_CLEANUP);
+ cleanup = null;
+ }
+
+ public Semaphore getSemaphore() {
+ return semaphore;
+ }
+
+ public boolean isCleaned() {
+ return semaphore.availablePermits() != 0;
+ }
+
+ private synchronized void addEvent(int e) {
+ events[eventNdx++] = e;
+ }
+
+ /**
+ * Computed the expected result from the sequence of events.
+ * If EV_CLEAR appears before anything else, it is cleared.
+ * If EV_CLEAN appears before EV_UNREF, then it is cleaned.
+ * Anything else is Unknown.
+ * @return EV_CLEAR if the cleanup should occur;
+ * EV_CLEAN if the cleanup should occur;
+ * EV_UNKNOWN if it is unknown.
+ */
+ public synchronized int expectedResult() {
+ // Test if EV_CLEAR appears before anything else
+ int clearNdx = indexOfEvent(EV_CLEAR);
+ int cleanNdx = indexOfEvent(EV_CLEAN);
+ int unrefNdx = indexOfEvent(EV_UNREF);
+ if (clearNdx < cleanNdx) {
+ return EV_CLEAR;
+ }
+ if (cleanNdx < clearNdx || cleanNdx < unrefNdx) {
+ return EV_CLEAN;
+ }
+ if (unrefNdx < eventNdx) {
+ return EV_CLEAN;
+ }
+
+ return EV_UNKNOWN;
+ }
+
+ private synchronized int indexOfEvent(int e) {
+ for (int i = 0; i < eventNdx; i++) {
+ if (events[i] == e) {
+ return i;
+ }
+ }
+ return eventNdx;
+ }
+
+ private static final String[] names =
+ {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"};
+
+ public String eventName(int event) {
+ return names[event];
+ }
+
+ public synchronized String eventsString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for (int i = 0; i < eventNdx; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(eventName(events[i]));
+ }
+ sb.append(']');
+ sb.append(", throwEx: ");
+ sb.append(throwsEx);
+ return sb.toString();
+ }
+
+ public String toString() {
+ return String.format("Case: %s, expect: %s, events: %s",
+ getRef().getClass().getName(),
+ eventName(expectedResult()), eventsString());
+ }
+ }
+
+
+ /**
+ * Example using a Cleaner to remove WeakKey references from a Map.
+ */
+ @Test
+ void testWeakKey() {
+ ConcurrentHashMap<WeakKey<String>, String> map = new ConcurrentHashMap<>();
+ Cleaner cleaner = Cleaner.create();
+ String key = new String("foo"); // ensure it is not interned
+ String data = "bar";
+
+ map.put(new WeakKey<>(key, cleaner, map), data);
+
+ WeakKey<String> k2 = new WeakKey<>(key, cleaner, map);
+
+ Assert.assertEquals(map.get(k2), data, "value should be found in the map");
+ key = null;
+ System.gc();
+ Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map");
+
+ final int CYCLE_MAX = 30;
+ for (int i = 1; map.size() > 0 && i < CYCLE_MAX; i++) {
+ map.forEach( (k, v) -> System.out.printf(" k: %s, v: %s%n", k, v));
+ try {
+ Thread.sleep(10L);
+ } catch (InterruptedException ie) {}
+ }
+ Assert.assertEquals(map.size(), 0, "Expected map to be empty;");
+ cleaner = null;
+ }
+
+ /**
+ * Test sample class for WeakKeys in Map.
+ * @param <K> A WeakKey of type K
+ */
+ class WeakKey<K> extends WeakReference<K> {
+ private final int hash;
+ private final ConcurrentHashMap<WeakKey<K>, ?> map;
+ Cleaner.Cleanable cleanable;
+
+ public WeakKey(K key, Cleaner c, ConcurrentHashMap<WeakKey<K>, ?> map) {
+ super(key);
+ this.hash = key.hashCode();
+ this.map = map;
+ cleanable = new WeakCleanable<Object>(key, c) {
+ protected void performCleanup() {
+ map.remove(WeakKey.this);
+ }
+ };
+ }
+ public int hashCode() { return hash; }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof WeakKey)) return false;
+ K key = get();
+ if (key == null) return obj == this;
+ return key == ((WeakKey<?>)obj).get();
+ }
+
+ public String toString() {
+ return "WeakKey:" + Objects.toString(get() + ", cleanableRef: " +
+ ((Reference)cleanable).get());
+ }
+ }
+
+ /**
+ * Verify that casting a Cleanup to a Reference is not allowed to
+ * get the referent or clear the reference.
+ */
+ @Test
+ @SuppressWarnings("rawtypes")
+ void testReferentNotAvailable() {
+ Cleaner cleaner = Cleaner.create();
+ Semaphore s1 = new Semaphore(0);
+
+ Object obj = new String("a new string");
+ Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release());
+ Reference r = (Reference) c;
+ try {
+ Object o = r.get();
+ System.out.printf("r: %s%n", Objects.toString(o));
+ Assert.fail("should not be able to get the referent from Cleanable");
+ } catch (UnsupportedOperationException uoe) {
+ // expected
+ }
+
+ try {
+ r.clear();
+ Assert.fail("should not be able to clear the referent from Cleanable");
+ } catch (UnsupportedOperationException uoe) {
+ // expected
+ }
+
+ obj = null;
+ Assert.assertTrue(checkCleaned(s1), "reference should be cleaned;");
+ cleaner = null;
+ }
+
+}