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