changeset 13340:7c8488605485

8138696: java.lang.ref.Cleaner - an easy to use alternative to finalization Reviewed-by: mchung, mr, chegar, plevart, kbarrett, dholmes
author rriggs
date Mon, 21 Dec 2015 11:34:14 -0500
parents 117b25ef4717
children 0e95eebcfb29
files src/java.base/share/classes/java/lang/ref/Cleaner.java src/java.base/share/classes/java/lang/ref/package-info.java src/java.base/share/classes/jdk/internal/misc/CleanerImpl.java test/java/lang/ref/CleanerTest.java
diffstat 4 files changed, 1759 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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();
+    }
+
+}
--- a/src/java.base/share/classes/java/lang/ref/package-info.java	Mon Dec 21 13:30:58 2015 +0100
+++ b/src/java.base/share/classes/java/lang/ref/package-info.java	Mon Dec 21 11:34:14 2015 -0500
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2003, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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
@@ -45,6 +45,8 @@
  * (or values) from being reclaimed, and phantom references are for
  * scheduling pre-mortem cleanup actions in a more flexible way than
  * is possible with the Java finalization mechanism.
+ * Post-mortem cleanup actions can be registered and managed by a
+ * {@link java.lang.ref.Cleaner}.
  *
  * <p> Each reference-object type is implemented by a subclass of the
  * abstract base {@link java.lang.ref.Reference} class.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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/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;
+    }
+
+}