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