< prev index next >

test/java/lang/ref/CleanerTest.java

Print this page

        

@@ -19,36 +19,34 @@
  * 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 jdk.internal.ref.CleanerFactory;
+import jdk.internal.ref.CleanerImpl;
+import jdk.internal.ref.PhantomCleanable;
+import jdk.internal.ref.SoftCleanable;
+import jdk.internal.ref.WeakCleanable;
+import jdk.test.lib.Utils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import sun.hotspot.WhiteBox;
+
 import java.lang.ref.Cleaner;
-import java.lang.ref.Reference;
 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.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import jdk.internal.ref.PhantomCleanable;
-import jdk.internal.ref.WeakCleanable;
-import jdk.internal.ref.SoftCleanable;
-import jdk.internal.ref.CleanerFactory;
-
-import sun.hotspot.WhiteBox;
-
-import jdk.test.lib.Utils;
-
-import org.testng.Assert;
-import org.testng.TestNG;
-import org.testng.annotations.Test;
-
 /*
  * @test
  * @library /test/lib/share/classes /lib/testlibrary /test/lib
  * @build sun.hotspot.WhiteBox
  * @build jdk.test.lib.Utils

@@ -83,20 +81,25 @@
     @Test
     @SuppressWarnings("unchecked")
     void testCleanableActions() {
         Cleaner cleaner = Cleaner.create();
 
+        // No actions
+        generateCases(cleaner);
+
         // Individually
-        generateCases(cleaner, c -> c.clearRef());
-        generateCases(cleaner, c -> c.doClean());
+        generateCases(cleaner, CleanableCase::doClean);
+        generateCases(cleaner, CleanableCase::releaseReferent);
 
         // Pairs
-        generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
+        generateCases(cleaner,
+                      CleanableCase::doClean,
+                      CleanableCase::releaseReferent);
 
-        CleanableCase s = setupPhantom(COMMON, cleaner);
+        CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent();
         cleaner = null;
-        checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
+        checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:");
     }
 
     /**
      * Test the jdk.internal.misc APIs with sequences of the various actions
      * on a Reference and on the Cleanable instance have the desired result.

@@ -110,28 +113,42 @@
     @Test
     @SuppressWarnings("unchecked")
     void testRefSubtypes() {
         Cleaner cleaner = Cleaner.create();
 
+        // No acions
+        generateCasesInternal(cleaner);
+
         // Individually
-        generateCasesInternal(cleaner, c -> c.clearRef());
-        generateCasesInternal(cleaner, c -> c.doClean());
-        generateCasesInternal(cleaner, c -> c.doClear());
+        generateCasesInternal(cleaner, CleanableCase::doClear);
+        generateCasesInternal(cleaner, CleanableCase::doClean);
+        generateCasesInternal(cleaner, CleanableCase::releaseReferent);
 
         // Pairs
         generateCasesInternal(cleaner,
-                c -> c.doClear(), c -> c.doClean());
+                              CleanableCase::doClear,
+                              CleanableCase::doClean);
+
+        generateCasesInternal(cleaner,
+                              CleanableCase::doClear,
+                              CleanableCase::releaseReferent);
+
+        generateCasesInternal(cleaner,
+                              CleanableCase::doClean,
+                              CleanableCase::releaseReferent);
 
         // Triplets
         generateCasesInternal(cleaner,
-                c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
+                              CleanableCase::doClear,
+                              CleanableCase::doClean,
+                              CleanableCase::releaseReferent);
 
         generateExceptionCasesInternal(cleaner);
 
-        CleanableCase s = setupPhantom(COMMON, cleaner);
+        CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent();
         cleaner = null;
-        checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
+        checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:");
     }
 
     /**
      * Generate tests using the runnables for each of phantom, weak,
      * and soft references.

@@ -154,15 +171,15 @@
     }
 
     @SuppressWarnings("unchecked")
     void generateExceptionCasesInternal(Cleaner cleaner) {
         generateCases(() -> setupPhantomSubclassException(cleaner, null),
-                1, c -> c.clearRef());
+                      1, CleanableCase::releaseReferent);
         generateCases(() -> setupWeakSubclassException(cleaner, null),
-                1, c -> c.clearRef());
+                      1, CleanableCase::releaseReferent);
         generateCases(() -> setupSoftSubclassException(cleaner, null),
-                1, c -> c.clearRef());
+                      1, CleanableCase::releaseReferent);
     }
 
     /**
      * Generate all permutations of the sequence of runnables
      * and test each one.

@@ -172,11 +189,11 @@
      * @param runnables the sequence of actions
      */
     @SuppressWarnings("unchecked")
     void generateCases(Supplier<CleanableCase> generator, int n,
                        Consumer<CleanableCase> ... runnables) {
-        if (n == 1) {
+        if (n <= 1) {
             CleanableCase test = generator.get();
             try {
                 verifyGetRef(test);
 
                 // Apply the sequence of actions on the Ref

@@ -209,20 +226,30 @@
      * The Cleanable itself should have been cleanedup.
      *
      * @param test A CleanableCase containing the references
      */
     void verify(CleanableCase test) {
+
+        CleanableCase cc = setupPhantom(COMMON, test.getCleanable()).releaseReferent();
+        // release Cleanable if impl. class
+        test.releaseCleanableIfImpl();
+
         System.out.println(test);
-        int r = test.expectedResult();
+        int expectedCleanups = test.expectedCleanups();
+
+        checkCleaned(test.getSemaphore(), expectedCleanups,
+                test.cleanableDescr + " was cleaned:");
+
+        // release Cleanable unconditionally
+        test.releaseCleanable();
 
-        CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
-        test.clearCleanable();        // release this hard reference
+        // only if the Cleanable is a CleanerImpl.XxxCleanableImpl class and
+        // it was not cleaned it will remain reachable...
+        int expectedCleanableCleanups =
+            (test.isCleanableImplClass && expectedCleanups == 0) ? 0 : 1;
 
-        checkCleaned(test.getSemaphore(),
-                r == CleanableCase.EV_CLEAN,
-                "Cleanable was cleaned:");
-        checkCleaned(cc.getSemaphore(), true,
+        checkCleaned(cc.getSemaphore(), expectedCleanableCleanups,
                 "The reference to the Cleanable was freed:");
     }
 
     /**
      * Verify that the reference.get works (or not) as expected.

@@ -232,27 +259,38 @@
      */
     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")) {
+            if (isCleanableImplClass(r.getClass())) {
                 Assert.fail("should not be able to get referent");
             }
+            Object expected = r instanceof PhantomReference
+                              ? null
+                              : test.getReferent();
+            Assert.assertEquals(expected, o,
+                    "Object reference incorrect");
         } catch (UnsupportedOperationException uoe) {
-            if (r.getClass().getName().endsWith("CleanableRef")) {
+            if (isCleanableImplClass(r.getClass())) {
                 // Expected exception
             } else {
                 Assert.fail("Unexpected exception from subclassed cleanable: " +
                         uoe.getMessage() + ", class: " + r.getClass());
             }
         }
     }
 
     /**
+     * @return true if given clazz is one of the CleanerImpl.XxxCleanableImpl
+     *         classes
+     */
+    static boolean isCleanableImplClass(Class<?> clazz) {
+        return Cleaner.Cleanable.class.isAssignableFrom(clazz) &&
+               clazz.getEnclosingClass() == CleanerImpl.class;
+    }
+
+    /**
      * Test that releasing the reference to the Cleaner service allows it to be
      * be freed.
      */
     @Test
     void testCleanerTermination() {

@@ -272,39 +310,66 @@
             System.out.printf("queue.remove Interrupted%n");
         }
     }
 
     /**
-     * Check a semaphore having been released by cleanup handler.
+     * Check a semaphore having been released by cleanup handler for the number of times.
      * Force a number of GC cycles to give the GC a chance to process
      * the Reference and for the cleanup action to be run.
      * Use a larger number of cycles to wait for an expected cleaning to occur.
      *
      * @param semaphore a Semaphore
-     * @param expectCleaned true if cleaning should occur
+     * @param expectedCleanups # of expected cleanups to occur
      * @param msg a message to explain the error
      */
-    static void checkCleaned(Semaphore semaphore, boolean expectCleaned,
+    static void checkCleaned(Semaphore semaphore, int expectedCleanups,
                              String msg) {
-        long max_cycles = expectCleaned ? 10 : 3;
+        if (expectedCleanups < 0) {
+            // can't predict - anything is OK
+            whitebox.fullGC();
+            int acquired = drain(semaphore);
+            System.out.printf(msg + " %d times\n", acquired);
+            return;
+        }
+
+        long max_cycles = expectedCleanups > 0 ? 10 : 3;
+        // wait for at least 1 permit
+        int permits = Math.max(1, expectedCleanups);
         long cycle = 0;
         for (; cycle < max_cycles; cycle++) {
             // Force GC
             whitebox.fullGC();
 
             try {
-                if (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
-                    System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle);
-                    Assert.assertEquals(true, expectCleaned, msg);
+                if (semaphore.tryAcquire(permits,
+                                         Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
+                    int acquired = drain(semaphore) + permits;
+                    System.out.printf(msg + " %d times in cycle %d\n", acquired, cycle);
+                    Assert.assertEquals(acquired, expectedCleanups, msg);
                     return;
                 }
             } catch (InterruptedException ie) {
                 // retry in outer loop
             }
         }
-        // Object has not been cleaned
-        Assert.assertEquals(false, expectCleaned, msg);
+        // cleanup has not been invoked for at least 'permits' # of times
+        int acquired = drain(semaphore);
+        System.out.printf(msg + " %d times\n", acquired);
+        Assert.assertEquals(acquired, expectedCleanups, msg);
+    }
+
+    // like semaphore.drain() but wait a while for a permit to be available
+    private static int drain(Semaphore semaphore) {
+        int acquired = 0;
+        try {
+            while (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
+                acquired++;
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+        return acquired;
     }
 
     /**
      * Create a CleanableCase for a PhantomReference.
      * @param cleaner the cleaner to use

@@ -314,13 +379,13 @@
     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());
+        Cleaner.Cleanable c1 = cleaner.register(obj, s1::release);
 
-        return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+        return new CleanableCase(obj, c1, s1);
     }
 
     /**
      * Create a CleanableCase for a PhantomReference.
      * @param cleaner the cleaner to use

@@ -331,17 +396,17 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
             }
         };
 
-        return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
+        return new CleanableCase(obj, 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

@@ -351,17 +416,17 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
             }
         };
 
-        return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
+        return new CleanableCase(obj, c1, s1);
     }
 
     /**
      * Create a CleanableCase for a SoftReference.
      * @param cleaner the cleaner to use

@@ -372,17 +437,17 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new SoftCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
             }
         };
 
-        return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
+        return new CleanableCase(obj, c1, s1);
     }
 
     /**
      * Create a CleanableCase for a PhantomReference.
      * @param cleaner the cleaner to use

@@ -393,18 +458,18 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
                 throw new RuntimeException("Exception thrown to cleaner thread");
             }
         };
 
-        return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
+        return new CleanableCase(obj, c1, s1, true);
     }
 
     /**
      * Create a CleanableCase for a WeakReference.
      * @param cleaner the cleaner to use

@@ -415,18 +480,18 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
                 throw new RuntimeException("Exception thrown to cleaner thread");
             }
         };
 
-        return new CleanableCase(new WeakReference<>(obj, null), c1, s1, true);
+        return new CleanableCase(obj, c1, s1, true);
     }
 
     /**
      * Create a CleanableCase for a SoftReference.
      * @param cleaner the cleaner to use

@@ -437,18 +502,18 @@
         if (obj == null) {
             obj = new Object();
         }
         Semaphore s1 = new Semaphore(0);
 
-        Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
-            protected void performCleanup() {
+        Cleaner.Cleanable c1 = new SoftCleanable<>(obj, cleaner) {
+            public void clean() {
                 s1.release();
                 throw new RuntimeException("Exception thrown to cleaner thread");
             }
         };
 
-        return new CleanableCase(new SoftReference<>(obj, null), c1, s1, true);
+        return new CleanableCase(obj, c1, s1, true);
     }
 
     /**
      * CleanableCase encapsulates the objects used for a test.
      * The reference to the object is not held directly,

@@ -458,153 +523,182 @@
      * 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 volatile Object referent;
+        private volatile Cleaner.Cleanable cleanable;
         private final Semaphore semaphore;
         private final boolean throwsEx;
-        private final int[] events;   // Sequence of calls to clean, clear, etc.
+        final String cleanableDescr;
+        final boolean isCleanableImplClass;
+        private final Event[] 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;
+        enum Event {
+            UNKNOWN, CLEAR, CLEAN, RELEASE_REFERENT, RELEASE_CLEANABLE;
+
+            boolean isBeforeIn(Event e, Event[] events) {
+                return this.indexIn(events) < e.indexIn(events);
+            }
+
+            boolean isPresentIn(Event[] events) {
+                return this.indexIn(events) < events.length;
+            }
 
+            int countOccurrencesIn(Event[] events) {
+                int count = 0;
+                for (int i = 0; i < events.length && events[i] != null; i++) {
+                    if (events[i] == this) {
+                        count++;
+                    }
+                }
+                return count;
+            }
 
-        CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+            private int indexIn(Event[] events) {
+                for (int i = 0; i < events.length && events[i] != null; i++) {
+                    if (events[i] == this) {
+                        return i;
+                    }
+                }
+                return events.length;
+            }
+        }
+
+        CleanableCase(Object referent, Cleaner.Cleanable cleanable,
                       Semaphore semaphore) {
-            this.ref = ref;
-            this.cleanup = cleanup;
-            this.semaphore = semaphore;
-            this.throwsEx = false;
-            this.events = new int[4];
-            this.eventNdx = 0;
+            this(referent, cleanable, semaphore, false);
         }
-        CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
+        CleanableCase(Object referent, Cleaner.Cleanable cleanable,
                       Semaphore semaphore,
                       boolean throwsEx) {
-            this.ref = ref;
-            this.cleanup = cleanup;
+            this.referent = referent;
+            this.cleanable = cleanable;
             this.semaphore = semaphore;
             this.throwsEx = throwsEx;
-            this.events = new int[4];
+            this.isCleanableImplClass = isCleanableImplClass(cleanable.getClass());
+            this.cleanableDescr = cleanable.getClass().isAnonymousClass()
+                                  ? cleanable.getClass().getSuperclass().getName()
+                                  : cleanable.getClass().getName();
+            this.events = new Event[5];
             this.eventNdx = 0;
         }
 
-        public Reference<?> getRef() {
-            return ref;
+        public Object getReferent() {
+            return referent;
         }
 
-        public void clearRef() {
-            addEvent(EV_UNREF);
-            ref.clear();
+        public CleanableCase releaseReferent() {
+            if (referent != null) {
+                addEvent(Event.RELEASE_REFERENT);
+                referent = null;
+            }
+            return this;
         }
 
         public Cleaner.Cleanable getCleanable() {
-            return cleanup;
+            return cleanable;
         }
 
         public void doClean() {
             try {
-                addEvent(EV_CLEAN);
-                cleanup.clean();
+                addEvent(Event.CLEAN);
+                cleanable.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();
+            addEvent(Event.CLEAR);
+            ((Reference) cleanable).clear();
+        }
+
+        public void releaseCleanableIfImpl() {
+            if (isCleanableImplClass) {
+                // only the CleanerImpl.XxxCleanableImpl instances are guaranteed
+                // to be retained by CleanerImpl so we can only release them and
+                // still expect the Cleanable to be cleaned...
+                releaseCleanable();
+            }
         }
 
-        public void clearCleanable() {
-            addEvent(EV_CLEAR_CLEANUP);
-            cleanup = null;
+        public void releaseCleanable() {
+            if (cleanable != null) {
+                // unconditionally release any Cleanable
+                addEvent(Event.RELEASE_CLEANABLE);
+                cleanable = null;
+            }
         }
 
         public Semaphore getSemaphore() {
             return semaphore;
         }
 
         public boolean isCleaned() {
             return semaphore.availablePermits() != 0;
         }
 
-        private synchronized void addEvent(int e) {
+        private synchronized void addEvent(Event 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;
-                }
+         * Compute the number of expected cleanups to be triggered
+         * given the type of Cleanable implementation and the collected events.
+         * Return -1 if we can't predict the number.
+         */
+        public synchronized int expectedCleanups() {
+
+            if (isCleanableImplClass) { // the CleanerImpl.XxxCleanableImpl class
+
+                // doesn't matter how many or what the order of events was as long
+                // as either the clean() method was called explicitly or
+                // the referent was released, we expect exactly one cleanup action
+                // otherwise none
+                return Event.CLEAN.isPresentIn(events) ||
+                       Event.RELEASE_REFERENT.isPresentIn(events) ? 1 : 0;
+
+            } else { // plain XxxCleanable subclass
+
+                // each time clean() is invoked explicitly, a cleanup action is
+                // performed (no at-most-once semantics here)
+                int explicitCleanups = Event.CLEAN.countOccurrencesIn(events);
+
+                // if we clear()-ed the referent before releasing it,
+                // then that's all we get
+                if (Event.CLEAR.isBeforeIn(Event.RELEASE_REFERENT, events)) {
+                    return explicitCleanups;
+                }
+
+                // else only if Cleanable was not released and clear() was not
+                // called, can we expect any predictable behavior from plain
+                // XxxCleanable subclass...
+                if (!Event.RELEASE_CLEANABLE.isPresentIn(events) &&
+                    !Event.CLEAR.isPresentIn(events)) {
+                    // we get one additional cleanup to all the explicit ones
+                    // if referent was released...
+                    return explicitCleanups +
+                           (Event.RELEASE_REFERENT.isPresentIn(events)? 1 : 0);
             }
-            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];
+                // -1 means we can't predict
+                return -1;
+            }
         }
 
         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();
+            return Arrays.asList(Arrays.copyOf(events, eventNdx)).toString();
         }
 
         public String toString() {
-            return String.format("Case: %s, expect: %s, events: %s",
-                    getRef().getClass().getName(),
-                    eventName(expectedResult()), eventsString());
+            return String.format("Case: %s, expected cleanups: %s, events: %s",
+                                 cleanableDescr, expectedCleanups(), eventsString());
         }
     }
 
 
     /**

@@ -615,15 +709,19 @@
         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> k1 = new WeakKey<>(key, cleaner, map);
+        map.put(k1, data);
 
         WeakKey<String> k2 = new WeakKey<>(key, cleaner, map);
 
         Assert.assertEquals(map.get(k2), data, "value should be found in the map");
+        // ensure key is reachable until the lookup above
+        Reference.reachabilityFence(key);
+        // make key unreachable now
         key = null;
         System.gc();
         Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map");
 
         final long CYCLE_MAX = Utils.adjustTimeout(30L);

@@ -632,47 +730,47 @@
             try {
                 Thread.sleep(10L);
             } catch (InterruptedException ie) {}
         }
         Assert.assertEquals(map.size(), 0, "Expected map to be empty;");
-        cleaner = null;
+        // ensure cleaner an k1 remain reachable until the end of the test
+        Reference.reachabilityFence(cleaner);
+        Reference.reachabilityFence(k1);
     }
 
     /**
      * Test sample class for WeakKeys in Map.
      * @param <K> A WeakKey of type K
      */
-    class WeakKey<K> extends WeakReference<K> {
+    class WeakKey<K> extends WeakCleanable<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);
+            super(key, c);
             this.hash = key.hashCode();
             this.map = map;
-            cleanable = new WeakCleanable<Object>(key, c) {
-                protected void performCleanup() {
-                    map.remove(WeakKey.this);
                 }
-            };
+
+        @Override
+        public void clean() {
+            map.remove(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();
+            return key != null && key == ((WeakKey<?>)obj).get();
         }
 
         public String toString() {
-            return "WeakKey:" + Objects.toString(get() + ", cleanableRef: " +
-                    ((Reference)cleanable).get());
+            return "WeakKey:" + get();
         }
     }
 
     /**
      * Verify that casting a Cleanup to a Reference is not allowed to

@@ -700,24 +798,24 @@
             Assert.fail("should not be able to clear the referent from Cleanable");
         } catch (UnsupportedOperationException uoe) {
             // expected
         }
 
+        // ensure obj is reachable until now
+        Reference.reachabilityFence(obj);
+        // make it unreachable now
         obj = null;
-        checkCleaned(s1, true, "reference was cleaned:");
-        cleaner = null;
+        checkCleaned(s1, 1, "reference was cleaned:");
     }
 
     /**
      * Test the Cleaner from the CleanerFactory.
      */
     @Test
     void testCleanerFactory() {
         Cleaner cleaner = CleanerFactory.cleaner();
 
-        Object obj = new Object();
-        CleanableCase s = setupPhantom(cleaner, obj);
-        obj = null;
-        checkCleaned(s.getSemaphore(), true,
+        CleanableCase s = setupPhantom(cleaner, null).releaseReferent();
+        checkCleaned(s.getSemaphore(), 1,
                 "Object was cleaned using CleanerFactor.cleaner():");
     }
 }
< prev index next >