1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.lang.ref.Cleaner;
  25 import java.lang.ref.Reference;
  26 import java.lang.ref.PhantomReference;
  27 import java.lang.ref.ReferenceQueue;
  28 import java.lang.ref.SoftReference;
  29 import java.lang.ref.WeakReference;
  30 import java.util.Objects;
  31 import java.util.concurrent.ConcurrentHashMap;
  32 import java.util.concurrent.Semaphore;
  33 import java.util.concurrent.TimeUnit;
  34 import java.util.function.Consumer;
  35 import java.util.function.Supplier;
  36 
  37 import jdk.internal.ref.PhantomCleanable;
  38 import jdk.internal.ref.WeakCleanable;
  39 import jdk.internal.ref.SoftCleanable;
  40 import jdk.internal.ref.CleanerFactory;
  41 
  42 import sun.hotspot.WhiteBox;
  43 
  44 import jdk.test.lib.Utils;
  45 
  46 import org.testng.Assert;
  47 import org.testng.TestNG;
  48 import org.testng.annotations.Test;
  49 
  50 /*
  51  * @test
  52  * @library /test/lib/share/classes /lib/testlibrary /test/lib
  53  * @build sun.hotspot.WhiteBox
  54  * @build jdk.test.lib.Utils
  55  * @modules java.base/jdk.internal
  56  *          java.base/jdk.internal.misc
  57  *          java.base/jdk.internal.ref
  58  *          java.management
  59  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  60  * @run testng/othervm
  61  *      -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:.
  62  *      -verbose:gc CleanerTest
  63  */
  64 
  65 @Test
  66 public class CleanerTest {
  67     // A common CleaningService used by the test for notifications
  68     static final Cleaner COMMON = CleanerFactory.cleaner();
  69 
  70     // Access to WhiteBox utilities
  71     static final WhiteBox whitebox = WhiteBox.getWhiteBox();
  72 
  73     /**
  74      * Test that sequences of the various actions on a Reference
  75      * and on the Cleanable instance have the desired result.
  76      * The test cases are generated for each of phantom, weak and soft
  77      * references.
  78      * The sequence of actions includes all permutations to an initial
  79      * list of actions including clearing the ref and resulting garbage
  80      * collection actions on the reference and explicitly performing
  81      * the cleaning action.
  82      */
  83     @Test
  84     @SuppressWarnings("unchecked")
  85     void testCleanableActions() {
  86         Cleaner cleaner = Cleaner.create();
  87 
  88         // Individually
  89         generateCases(cleaner, c -> c.clearRef());
  90         generateCases(cleaner, c -> c.doClean());
  91 
  92         // Pairs
  93         generateCases(cleaner, c -> c.doClean(), c -> c.clearRef());
  94 
  95         CleanableCase s = setupPhantom(COMMON, cleaner);
  96         cleaner = null;
  97         checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
  98     }
  99 
 100     /**
 101      * Test the jdk.internal.misc APIs with sequences of the various actions
 102      * on a Reference and on the Cleanable instance have the desired result.
 103      * The test cases are generated for each of phantom, weak and soft
 104      * references.
 105      * The sequence of actions includes all permutations to an initial
 106      * list of actions including clearing the ref and resulting garbage
 107      * collection actions on the reference, explicitly performing
 108      * the cleanup and explicitly clearing the cleaning action.
 109      */
 110     @Test
 111     @SuppressWarnings("unchecked")
 112     void testRefSubtypes() {
 113         Cleaner cleaner = Cleaner.create();
 114 
 115         // Individually
 116         generateCasesInternal(cleaner, c -> c.clearRef());
 117         generateCasesInternal(cleaner, c -> c.doClean());
 118         generateCasesInternal(cleaner, c -> c.doClear());
 119 
 120         // Pairs
 121         generateCasesInternal(cleaner,
 122                 c -> c.doClear(), c -> c.doClean());
 123 
 124         // Triplets
 125         generateCasesInternal(cleaner,
 126                 c -> c.doClear(), c -> c.doClean(), c -> c.clearRef());
 127 
 128         generateExceptionCasesInternal(cleaner);
 129 
 130         CleanableCase s = setupPhantom(COMMON, cleaner);
 131         cleaner = null;
 132         checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:");
 133     }
 134 
 135     /**
 136      * Generate tests using the runnables for each of phantom, weak,
 137      * and soft references.
 138      * @param cleaner  the cleaner
 139      * @param runnables the sequence of actions on the test case
 140      */
 141     @SuppressWarnings("unchecked")
 142     void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
 143         generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables);
 144     }
 145 
 146     @SuppressWarnings("unchecked")
 147     void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) {
 148         generateCases(() -> setupPhantomSubclass(cleaner, null),
 149                 runnables.length, runnables);
 150         generateCases(() -> setupWeakSubclass(cleaner, null),
 151                 runnables.length, runnables);
 152         generateCases(() -> setupSoftSubclass(cleaner, null),
 153                 runnables.length, runnables);
 154     }
 155 
 156     @SuppressWarnings("unchecked")
 157     void generateExceptionCasesInternal(Cleaner cleaner) {
 158         generateCases(() -> setupPhantomSubclassException(cleaner, null),
 159                 1, c -> c.clearRef());
 160         generateCases(() -> setupWeakSubclassException(cleaner, null),
 161                 1, c -> c.clearRef());
 162         generateCases(() -> setupSoftSubclassException(cleaner, null),
 163                 1, c -> c.clearRef());
 164     }
 165 
 166     /**
 167      * Generate all permutations of the sequence of runnables
 168      * and test each one.
 169      * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges.
 170      * @param generator the supplier of a CleanableCase
 171      * @param n the first index to interchange
 172      * @param runnables the sequence of actions
 173      */
 174     @SuppressWarnings("unchecked")
 175     void generateCases(Supplier<CleanableCase> generator, int n,
 176                        Consumer<CleanableCase> ... runnables) {
 177         if (n == 1) {
 178             CleanableCase test = generator.get();
 179             try {
 180                 verifyGetRef(test);
 181 
 182                 // Apply the sequence of actions on the Ref
 183                 for (Consumer<CleanableCase> c : runnables) {
 184                     c.accept(test);
 185                 }
 186                 verify(test);
 187             } catch (Exception e) {
 188                 Assert.fail(test.toString(), e);
 189             }
 190         } else {
 191             for (int i = 0; i < n - 1; i += 1) {
 192                 generateCases(generator, n - 1, runnables);
 193                 Consumer<CleanableCase> t = runnables[n - 1];
 194                 int ndx = ((n & 1) == 0) ? i : 0;
 195                 runnables[n - 1] = runnables[ndx];
 196                 runnables[ndx] = t;
 197             }
 198             generateCases(generator, n - 1, runnables);
 199         }
 200     }
 201 
 202     /**
 203      * Verify the test case.
 204      * Any actions directly on the Reference or Cleanable have been executed.
 205      * The CleanableCase under test is given a chance to do the cleanup
 206      * by forcing a GC.
 207      * The result is compared with the expected result computed
 208      * from the sequence of operations on the Cleanable.
 209      * The Cleanable itself should have been cleanedup.
 210      *
 211      * @param test A CleanableCase containing the references
 212      */
 213     void verify(CleanableCase test) {
 214         System.out.println(test);
 215         int r = test.expectedResult();
 216 
 217         CleanableCase cc = setupPhantom(COMMON, test.getCleanable());
 218         test.clearCleanable();        // release this hard reference
 219 
 220         checkCleaned(test.getSemaphore(),
 221                 r == CleanableCase.EV_CLEAN,
 222                 "Cleanable was cleaned:");
 223         checkCleaned(cc.getSemaphore(), true,
 224                 "The reference to the Cleanable was freed:");
 225     }
 226 
 227     /**
 228      * Verify that the reference.get works (or not) as expected.
 229      * It handles the cases where UnsupportedOperationException is expected.
 230      *
 231      * @param test the CleanableCase
 232      */
 233     void verifyGetRef(CleanableCase test) {
 234         Reference<?> r = (Reference) test.getCleanable();
 235         try {
 236             Object o = r.get();
 237             Reference<?> expectedRef = test.getRef();
 238             Assert.assertEquals(expectedRef.get(), o,
 239                     "Object reference incorrect");
 240             if (r.getClass().getName().endsWith("CleanableRef")) {
 241                 Assert.fail("should not be able to get referent");
 242             }
 243         } catch (UnsupportedOperationException uoe) {
 244             if (r.getClass().getName().endsWith("CleanableRef")) {
 245                 // Expected exception
 246             } else {
 247                 Assert.fail("Unexpected exception from subclassed cleanable: " +
 248                         uoe.getMessage() + ", class: " + r.getClass());
 249             }
 250         }
 251     }
 252 
 253     /**
 254      * Test that releasing the reference to the Cleaner service allows it to be
 255      * be freed.
 256      */
 257     @Test
 258     void testCleanerTermination() {
 259         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 260         Cleaner service = Cleaner.create();
 261 
 262         PhantomReference<Object> ref = new PhantomReference<>(service, queue);
 263         System.gc();
 264         // Clear the Reference to the cleaning service and force a gc.
 265         service = null;
 266         System.gc();
 267         try {
 268             Reference<?> r = queue.remove(1000L);
 269             Assert.assertNotNull(r, "queue.remove timeout,");
 270             Assert.assertEquals(r, ref, "Wrong Reference dequeued");
 271         } catch (InterruptedException ie) {
 272             System.out.printf("queue.remove Interrupted%n");
 273         }
 274     }
 275 
 276     /**
 277      * Check a semaphore having been released by cleanup handler.
 278      * Force a number of GC cycles to give the GC a chance to process
 279      * the Reference and for the cleanup action to be run.
 280      * Use a larger number of cycles to wait for an expected cleaning to occur.
 281      *
 282      * @param semaphore a Semaphore
 283      * @param expectCleaned true if cleaning should occur
 284      * @param msg a message to explain the error
 285      */
 286     static void checkCleaned(Semaphore semaphore, boolean expectCleaned,
 287                              String msg) {
 288         long max_cycles = expectCleaned ? 10 : 3;
 289         long cycle = 0;
 290         for (; cycle < max_cycles; cycle++) {
 291             // Force GC
 292             whitebox.fullGC();
 293 
 294             try {
 295                 if (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) {
 296                     System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle);
 297                     Assert.assertEquals(true, expectCleaned, msg);
 298                     return;
 299                 }
 300             } catch (InterruptedException ie) {
 301                 // retry in outer loop
 302             }
 303         }
 304         // Object has not been cleaned
 305         Assert.assertEquals(false, expectCleaned, msg);
 306     }
 307 
 308     /**
 309      * Create a CleanableCase for a PhantomReference.
 310      * @param cleaner the cleaner to use
 311      * @param obj an object or null to create a new Object
 312      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 313      */
 314     static CleanableCase setupPhantom(Cleaner cleaner, Object obj) {
 315         if (obj == null) {
 316             obj = new Object();
 317         }
 318         Semaphore s1 = new Semaphore(0);
 319         Cleaner.Cleanable c1 = cleaner.register(obj, () -> s1.release());
 320 
 321         return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
 322     }
 323 
 324     /**
 325      * Create a CleanableCase for a PhantomReference.
 326      * @param cleaner the cleaner to use
 327      * @param obj an object or null to create a new Object
 328      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 329      */
 330     static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) {
 331         if (obj == null) {
 332             obj = new Object();
 333         }
 334         Semaphore s1 = new Semaphore(0);
 335 
 336         Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
 337             protected void performCleanup() {
 338                 s1.release();
 339             }
 340         };
 341 
 342         return new CleanableCase(new PhantomReference<>(obj, null), c1, s1);
 343     }
 344     /**
 345      * Create a CleanableCase for a WeakReference.
 346      * @param cleaner the cleaner to use
 347      * @param obj an object or null to create a new Object
 348      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 349      */
 350     static CleanableCase setupWeakSubclass(Cleaner cleaner, Object obj) {
 351         if (obj == null) {
 352             obj = new Object();
 353         }
 354         Semaphore s1 = new Semaphore(0);
 355 
 356         Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
 357             protected void performCleanup() {
 358                 s1.release();
 359             }
 360         };
 361 
 362         return new CleanableCase(new WeakReference<>(obj, null), c1, s1);
 363     }
 364 
 365     /**
 366      * Create a CleanableCase for a SoftReference.
 367      * @param cleaner the cleaner to use
 368      * @param obj an object or null to create a new Object
 369      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 370      */
 371     static CleanableCase setupSoftSubclass(Cleaner cleaner, Object obj) {
 372         if (obj == null) {
 373             obj = new Object();
 374         }
 375         Semaphore s1 = new Semaphore(0);
 376 
 377         Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
 378             protected void performCleanup() {
 379                 s1.release();
 380             }
 381         };
 382 
 383         return new CleanableCase(new SoftReference<>(obj, null), c1, s1);
 384     }
 385 
 386     /**
 387      * Create a CleanableCase for a PhantomReference.
 388      * @param cleaner the cleaner to use
 389      * @param obj an object or null to create a new Object
 390      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 391      */
 392     static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) {
 393         if (obj == null) {
 394             obj = new Object();
 395         }
 396         Semaphore s1 = new Semaphore(0);
 397 
 398         Cleaner.Cleanable c1 = new PhantomCleanable<Object>(obj, cleaner) {
 399             protected void performCleanup() {
 400                 s1.release();
 401                 throw new RuntimeException("Exception thrown to cleaner thread");
 402             }
 403         };
 404 
 405         return new CleanableCase(new PhantomReference<>(obj, null), c1, s1, true);
 406     }
 407 
 408     /**
 409      * Create a CleanableCase for a WeakReference.
 410      * @param cleaner the cleaner to use
 411      * @param obj an object or null to create a new Object
 412      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 413      */
 414     static CleanableCase setupWeakSubclassException(Cleaner cleaner, Object obj) {
 415         if (obj == null) {
 416             obj = new Object();
 417         }
 418         Semaphore s1 = new Semaphore(0);
 419 
 420         Cleaner.Cleanable c1 = new WeakCleanable<Object>(obj, cleaner) {
 421             protected void performCleanup() {
 422                 s1.release();
 423                 throw new RuntimeException("Exception thrown to cleaner thread");
 424             }
 425         };
 426 
 427         return new CleanableCase(new WeakReference<>(obj, null), c1, s1, true);
 428     }
 429 
 430     /**
 431      * Create a CleanableCase for a SoftReference.
 432      * @param cleaner the cleaner to use
 433      * @param obj an object or null to create a new Object
 434      * @return a new CleanableCase preset with the object, cleanup, and semaphore
 435      */
 436     static CleanableCase setupSoftSubclassException(Cleaner cleaner, Object obj) {
 437         if (obj == null) {
 438             obj = new Object();
 439         }
 440         Semaphore s1 = new Semaphore(0);
 441 
 442         Cleaner.Cleanable c1 = new SoftCleanable<Object>(obj, cleaner) {
 443             protected void performCleanup() {
 444                 s1.release();
 445                 throw new RuntimeException("Exception thrown to cleaner thread");
 446             }
 447         };
 448 
 449         return new CleanableCase(new SoftReference<>(obj, null), c1, s1, true);
 450     }
 451 
 452     /**
 453      * CleanableCase encapsulates the objects used for a test.
 454      * The reference to the object is not held directly,
 455      * but in a Reference object that can be cleared.
 456      * The semaphore is used to count whether the cleanup occurred.
 457      * It can be awaited on to determine that the cleanup has occurred.
 458      * It can be checked for non-zero to determine if it was
 459      * invoked or if it was invoked twice (a bug).
 460      */
 461     static class CleanableCase {
 462 
 463         private volatile Reference<?> ref;
 464         private volatile Cleaner.Cleanable cleanup;
 465         private final Semaphore semaphore;
 466         private final boolean throwsEx;
 467         private final int[] events;   // Sequence of calls to clean, clear, etc.
 468         private volatile int eventNdx;
 469 
 470         public static int EV_UNKNOWN = 0;
 471         public static int EV_CLEAR = 1;
 472         public static int EV_CLEAN = 2;
 473         public static int EV_UNREF = 3;
 474         public static int EV_CLEAR_CLEANUP = 4;
 475 
 476 
 477         CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
 478                       Semaphore semaphore) {
 479             this.ref = ref;
 480             this.cleanup = cleanup;
 481             this.semaphore = semaphore;
 482             this.throwsEx = false;
 483             this.events = new int[4];
 484             this.eventNdx = 0;
 485         }
 486         CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup,
 487                       Semaphore semaphore,
 488                       boolean throwsEx) {
 489             this.ref = ref;
 490             this.cleanup = cleanup;
 491             this.semaphore = semaphore;
 492             this.throwsEx = throwsEx;
 493             this.events = new int[4];
 494             this.eventNdx = 0;
 495         }
 496 
 497         public Reference<?> getRef() {
 498             return ref;
 499         }
 500 
 501         public void clearRef() {
 502             addEvent(EV_UNREF);
 503             ref.clear();
 504         }
 505 
 506         public Cleaner.Cleanable getCleanable() {
 507             return cleanup;
 508         }
 509 
 510         public void doClean() {
 511             try {
 512                 addEvent(EV_CLEAN);
 513                 cleanup.clean();
 514             } catch (RuntimeException ex) {
 515                 if (!throwsEx) {
 516                     // unless it is known this case throws an exception, rethrow
 517                     throw ex;
 518                 }
 519             }
 520         }
 521 
 522         public void doClear() {
 523             addEvent(EV_CLEAR);
 524             ((Reference)cleanup).clear();
 525         }
 526 
 527         public void clearCleanable() {
 528             addEvent(EV_CLEAR_CLEANUP);
 529             cleanup = null;
 530         }
 531 
 532         public Semaphore getSemaphore() {
 533             return semaphore;
 534         }
 535 
 536         public boolean isCleaned() {
 537             return semaphore.availablePermits() != 0;
 538         }
 539 
 540         private synchronized void addEvent(int e) {
 541             events[eventNdx++] = e;
 542         }
 543 
 544         /**
 545          * Computed the expected result from the sequence of events.
 546          * If EV_CLEAR appears before anything else, it is cleared.
 547          * If EV_CLEAN appears before EV_UNREF, then it is cleaned.
 548          * Anything else is Unknown.
 549          * @return EV_CLEAR if the cleanup should occur;
 550          *         EV_CLEAN if the cleanup should occur;
 551          *         EV_UNKNOWN if it is unknown.
 552          */
 553         public synchronized int expectedResult() {
 554             // Test if EV_CLEAR appears before anything else
 555             int clearNdx = indexOfEvent(EV_CLEAR);
 556             int cleanNdx = indexOfEvent(EV_CLEAN);
 557             int unrefNdx = indexOfEvent(EV_UNREF);
 558             if (clearNdx < cleanNdx) {
 559                 return EV_CLEAR;
 560             }
 561             if (cleanNdx < clearNdx || cleanNdx < unrefNdx) {
 562                 return EV_CLEAN;
 563             }
 564             if (unrefNdx < eventNdx) {
 565                 return EV_CLEAN;
 566             }
 567 
 568             return EV_UNKNOWN;
 569         }
 570 
 571         private synchronized  int indexOfEvent(int e) {
 572             for (int i = 0; i < eventNdx; i++) {
 573                 if (events[i] == e) {
 574                     return i;
 575                 }
 576             }
 577             return eventNdx;
 578         }
 579 
 580         private static final String[] names =
 581                 {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"};
 582 
 583         public String eventName(int event) {
 584             return names[event];
 585         }
 586 
 587         public synchronized String eventsString() {
 588             StringBuilder sb = new StringBuilder();
 589             sb.append('[');
 590             for (int i = 0; i < eventNdx; i++) {
 591                 if (i > 0) {
 592                     sb.append(", ");
 593                 }
 594                 sb.append(eventName(events[i]));
 595             }
 596             sb.append(']');
 597             sb.append(", throwEx: ");
 598             sb.append(throwsEx);
 599             return sb.toString();
 600         }
 601 
 602         public String toString() {
 603             return String.format("Case: %s, expect: %s, events: %s",
 604                     getRef().getClass().getName(),
 605                     eventName(expectedResult()), eventsString());
 606         }
 607     }
 608 
 609 
 610     /**
 611      * Example using a Cleaner to remove WeakKey references from a Map.
 612      */
 613     @Test
 614     void testWeakKey() {
 615         ConcurrentHashMap<WeakKey<String>, String> map = new ConcurrentHashMap<>();
 616         Cleaner cleaner = Cleaner.create();
 617         String key = new String("foo");  //  ensure it is not interned
 618         String data = "bar";
 619 
 620         map.put(new WeakKey<>(key, cleaner, map), data);
 621 
 622         WeakKey<String> k2 = new WeakKey<>(key, cleaner, map);
 623 
 624         Assert.assertEquals(map.get(k2), data, "value should be found in the map");
 625         key = null;
 626         System.gc();
 627         Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map");
 628 
 629         final long CYCLE_MAX = Utils.adjustTimeout(30L);
 630         for (int i = 1; map.size() > 0 && i < CYCLE_MAX; i++) {
 631             map.forEach( (k, v) -> System.out.printf("    k: %s, v: %s%n", k, v));
 632             try {
 633                 Thread.sleep(10L);
 634             } catch (InterruptedException ie) {}
 635         }
 636         Assert.assertEquals(map.size(), 0, "Expected map to be empty;");
 637         cleaner = null;
 638     }
 639 
 640     /**
 641      * Test sample class for WeakKeys in Map.
 642      * @param <K> A WeakKey of type K
 643      */
 644     class WeakKey<K> extends WeakReference<K> {
 645         private final int hash;
 646         private final ConcurrentHashMap<WeakKey<K>, ?> map;
 647         Cleaner.Cleanable cleanable;
 648 
 649         public WeakKey(K key, Cleaner c, ConcurrentHashMap<WeakKey<K>, ?> map) {
 650             super(key);
 651             this.hash = key.hashCode();
 652             this.map = map;
 653             cleanable = new WeakCleanable<Object>(key, c) {
 654                 protected void performCleanup() {
 655                     map.remove(WeakKey.this);
 656                 }
 657             };
 658         }
 659         public int hashCode() { return hash; }
 660 
 661         public boolean equals(Object obj) {
 662             if (obj == this) {
 663                 return true;
 664             }
 665             if (!(obj instanceof WeakKey)) return false;
 666             K key = get();
 667             if (key == null) return obj == this;
 668             return key == ((WeakKey<?>)obj).get();
 669         }
 670 
 671         public String toString() {
 672             return "WeakKey:" + Objects.toString(get() + ", cleanableRef: " +
 673                     ((Reference)cleanable).get());
 674         }
 675     }
 676 
 677     /**
 678      * Verify that casting a Cleanup to a Reference is not allowed to
 679      * get the referent or clear the reference.
 680      */
 681     @Test
 682     @SuppressWarnings("rawtypes")
 683     void testReferentNotAvailable() {
 684         Cleaner cleaner = Cleaner.create();
 685         Semaphore s1 = new Semaphore(0);
 686 
 687         Object obj = new String("a new string");
 688         Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release());
 689         Reference r = (Reference) c;
 690         try {
 691             Object o = r.get();
 692             System.out.printf("r: %s%n", Objects.toString(o));
 693             Assert.fail("should not be able to get the referent from Cleanable");
 694         } catch (UnsupportedOperationException uoe) {
 695             // expected
 696         }
 697 
 698         try {
 699             r.clear();
 700             Assert.fail("should not be able to clear the referent from Cleanable");
 701         } catch (UnsupportedOperationException uoe) {
 702             // expected
 703         }
 704 
 705         obj = null;
 706         checkCleaned(s1, true, "reference was cleaned:");
 707         cleaner = null;
 708     }
 709 
 710     /**
 711      * Test the Cleaner from the CleanerFactory.
 712      */
 713     @Test
 714     void testCleanerFactory() {
 715         Cleaner cleaner = CleanerFactory.cleaner();
 716 
 717         Object obj = new Object();
 718         CleanableCase s = setupPhantom(cleaner, obj);
 719         obj = null;
 720         checkCleaned(s.getSemaphore(), true,
 721                 "Object was cleaned using CleanerFactor.cleaner():");
 722     }
 723 }