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 } | 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 jdk.internal.ref.CleanerFactory; 25 import jdk.internal.ref.CleanerImpl; 26 import jdk.internal.ref.PhantomCleanable; 27 import jdk.internal.ref.SoftCleanable; 28 import jdk.internal.ref.WeakCleanable; 29 import jdk.test.lib.Utils; 30 import org.testng.Assert; 31 import org.testng.annotations.Test; 32 import sun.hotspot.WhiteBox; 33 34 import java.lang.ref.Cleaner; 35 import java.lang.ref.PhantomReference; 36 import java.lang.ref.Reference; 37 import java.lang.ref.ReferenceQueue; 38 import java.lang.ref.SoftReference; 39 import java.lang.ref.WeakReference; 40 import java.util.Arrays; 41 import java.util.Objects; 42 import java.util.concurrent.ConcurrentHashMap; 43 import java.util.concurrent.Semaphore; 44 import java.util.concurrent.TimeUnit; 45 import java.util.function.Consumer; 46 import java.util.function.Supplier; 47 48 /* 49 * @test 50 * @library /test/lib/share/classes /lib/testlibrary /test/lib 51 * @build sun.hotspot.WhiteBox 52 * @build jdk.test.lib.Utils 53 * @modules java.base/jdk.internal 54 * java.base/jdk.internal.misc 55 * java.base/jdk.internal.ref 56 * java.management 57 * @run main ClassFileInstaller sun.hotspot.WhiteBox 58 * @run testng/othervm 59 * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. 60 * -verbose:gc CleanerTest 61 */ 62 63 @Test 64 public class CleanerTest { 65 // A common CleaningService used by the test for notifications 66 static final Cleaner COMMON = CleanerFactory.cleaner(); 67 68 // Access to WhiteBox utilities 69 static final WhiteBox whitebox = WhiteBox.getWhiteBox(); 70 71 /** 72 * Test that sequences of the various actions on a Reference 73 * and on the Cleanable instance have the desired result. 74 * The test cases are generated for each of phantom, weak and soft 75 * references. 76 * The sequence of actions includes all permutations to an initial 77 * list of actions including clearing the ref and resulting garbage 78 * collection actions on the reference and explicitly performing 79 * the cleaning action. 80 */ 81 @Test 82 @SuppressWarnings("unchecked") 83 void testCleanableActions() { 84 Cleaner cleaner = Cleaner.create(); 85 86 // No actions 87 generateCases(cleaner); 88 89 // Individually 90 generateCases(cleaner, CleanableCase::doClean); 91 generateCases(cleaner, CleanableCase::releaseReferent); 92 93 // Pairs 94 generateCases(cleaner, 95 CleanableCase::doClean, 96 CleanableCase::releaseReferent); 97 98 CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent(); 99 cleaner = null; 100 checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:"); 101 } 102 103 /** 104 * Test the jdk.internal.misc APIs with sequences of the various actions 105 * on a Reference and on the Cleanable instance have the desired result. 106 * The test cases are generated for each of phantom, weak and soft 107 * references. 108 * The sequence of actions includes all permutations to an initial 109 * list of actions including clearing the ref and resulting garbage 110 * collection actions on the reference, explicitly performing 111 * the cleanup and explicitly clearing the cleaning action. 112 */ 113 @Test 114 @SuppressWarnings("unchecked") 115 void testRefSubtypes() { 116 Cleaner cleaner = Cleaner.create(); 117 118 // No acions 119 generateCasesInternal(cleaner); 120 121 // Individually 122 generateCasesInternal(cleaner, CleanableCase::doClear); 123 generateCasesInternal(cleaner, CleanableCase::doClean); 124 generateCasesInternal(cleaner, CleanableCase::releaseReferent); 125 126 // Pairs 127 generateCasesInternal(cleaner, 128 CleanableCase::doClear, 129 CleanableCase::doClean); 130 131 generateCasesInternal(cleaner, 132 CleanableCase::doClear, 133 CleanableCase::releaseReferent); 134 135 generateCasesInternal(cleaner, 136 CleanableCase::doClean, 137 CleanableCase::releaseReferent); 138 139 // Triplets 140 generateCasesInternal(cleaner, 141 CleanableCase::doClear, 142 CleanableCase::doClean, 143 CleanableCase::releaseReferent); 144 145 generateExceptionCasesInternal(cleaner); 146 147 CleanableCase s = setupPhantom(COMMON, cleaner).releaseReferent(); 148 cleaner = null; 149 checkCleaned(s.getSemaphore(), 1, "Cleaner was cleaned:"); 150 } 151 152 /** 153 * Generate tests using the runnables for each of phantom, weak, 154 * and soft references. 155 * @param cleaner the cleaner 156 * @param runnables the sequence of actions on the test case 157 */ 158 @SuppressWarnings("unchecked") 159 void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) { 160 generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables); 161 } 162 163 @SuppressWarnings("unchecked") 164 void generateCasesInternal(Cleaner cleaner, Consumer<CleanableCase>... runnables) { 165 generateCases(() -> setupPhantomSubclass(cleaner, null), 166 runnables.length, runnables); 167 generateCases(() -> setupWeakSubclass(cleaner, null), 168 runnables.length, runnables); 169 generateCases(() -> setupSoftSubclass(cleaner, null), 170 runnables.length, runnables); 171 } 172 173 @SuppressWarnings("unchecked") 174 void generateExceptionCasesInternal(Cleaner cleaner) { 175 generateCases(() -> setupPhantomSubclassException(cleaner, null), 176 1, CleanableCase::releaseReferent); 177 generateCases(() -> setupWeakSubclassException(cleaner, null), 178 1, CleanableCase::releaseReferent); 179 generateCases(() -> setupSoftSubclassException(cleaner, null), 180 1, CleanableCase::releaseReferent); 181 } 182 183 /** 184 * Generate all permutations of the sequence of runnables 185 * and test each one. 186 * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges. 187 * @param generator the supplier of a CleanableCase 188 * @param n the first index to interchange 189 * @param runnables the sequence of actions 190 */ 191 @SuppressWarnings("unchecked") 192 void generateCases(Supplier<CleanableCase> generator, int n, 193 Consumer<CleanableCase> ... runnables) { 194 if (n <= 1) { 195 CleanableCase test = generator.get(); 196 try { 197 verifyGetRef(test); 198 199 // Apply the sequence of actions on the Ref 200 for (Consumer<CleanableCase> c : runnables) { 201 c.accept(test); 202 } 203 verify(test); 204 } catch (Exception e) { 205 Assert.fail(test.toString(), e); 206 } 207 } else { 208 for (int i = 0; i < n - 1; i += 1) { 209 generateCases(generator, n - 1, runnables); 210 Consumer<CleanableCase> t = runnables[n - 1]; 211 int ndx = ((n & 1) == 0) ? i : 0; 212 runnables[n - 1] = runnables[ndx]; 213 runnables[ndx] = t; 214 } 215 generateCases(generator, n - 1, runnables); 216 } 217 } 218 219 /** 220 * Verify the test case. 221 * Any actions directly on the Reference or Cleanable have been executed. 222 * The CleanableCase under test is given a chance to do the cleanup 223 * by forcing a GC. 224 * The result is compared with the expected result computed 225 * from the sequence of operations on the Cleanable. 226 * The Cleanable itself should have been cleanedup. 227 * 228 * @param test A CleanableCase containing the references 229 */ 230 void verify(CleanableCase test) { 231 232 CleanableCase cc = setupPhantom(COMMON, test.getCleanable()).releaseReferent(); 233 // release Cleanable if impl. class 234 test.releaseCleanableIfImpl(); 235 236 System.out.println(test); 237 int expectedCleanups = test.expectedCleanups(); 238 239 checkCleaned(test.getSemaphore(), expectedCleanups, 240 test.cleanableDescr + " was cleaned:"); 241 242 // release Cleanable unconditionally 243 test.releaseCleanable(); 244 245 // only if the Cleanable is a CleanerImpl.XxxCleanableImpl class and 246 // it was not cleaned it will remain reachable... 247 int expectedCleanableCleanups = 248 (test.isCleanableImplClass && expectedCleanups == 0) ? 0 : 1; 249 250 checkCleaned(cc.getSemaphore(), expectedCleanableCleanups, 251 "The reference to the Cleanable was freed:"); 252 } 253 254 /** 255 * Verify that the reference.get works (or not) as expected. 256 * It handles the cases where UnsupportedOperationException is expected. 257 * 258 * @param test the CleanableCase 259 */ 260 void verifyGetRef(CleanableCase test) { 261 Reference<?> r = (Reference) test.getCleanable(); 262 try { 263 Object o = r.get(); 264 if (isCleanableImplClass(r.getClass())) { 265 Assert.fail("should not be able to get referent"); 266 } 267 Object expected = r instanceof PhantomReference 268 ? null 269 : test.getReferent(); 270 Assert.assertEquals(expected, o, 271 "Object reference incorrect"); 272 } catch (UnsupportedOperationException uoe) { 273 if (isCleanableImplClass(r.getClass())) { 274 // Expected exception 275 } else { 276 Assert.fail("Unexpected exception from subclassed cleanable: " + 277 uoe.getMessage() + ", class: " + r.getClass()); 278 } 279 } 280 } 281 282 /** 283 * @return true if given clazz is one of the CleanerImpl.XxxCleanableImpl 284 * classes 285 */ 286 static boolean isCleanableImplClass(Class<?> clazz) { 287 return Cleaner.Cleanable.class.isAssignableFrom(clazz) && 288 clazz.getEnclosingClass() == CleanerImpl.class; 289 } 290 291 /** 292 * Test that releasing the reference to the Cleaner service allows it to be 293 * be freed. 294 */ 295 @Test 296 void testCleanerTermination() { 297 ReferenceQueue<Object> queue = new ReferenceQueue<>(); 298 Cleaner service = Cleaner.create(); 299 300 PhantomReference<Object> ref = new PhantomReference<>(service, queue); 301 System.gc(); 302 // Clear the Reference to the cleaning service and force a gc. 303 service = null; 304 System.gc(); 305 try { 306 Reference<?> r = queue.remove(1000L); 307 Assert.assertNotNull(r, "queue.remove timeout,"); 308 Assert.assertEquals(r, ref, "Wrong Reference dequeued"); 309 } catch (InterruptedException ie) { 310 System.out.printf("queue.remove Interrupted%n"); 311 } 312 } 313 314 /** 315 * Check a semaphore having been released by cleanup handler for the number of times. 316 * Force a number of GC cycles to give the GC a chance to process 317 * the Reference and for the cleanup action to be run. 318 * Use a larger number of cycles to wait for an expected cleaning to occur. 319 * 320 * @param semaphore a Semaphore 321 * @param expectedCleanups # of expected cleanups to occur 322 * @param msg a message to explain the error 323 */ 324 static void checkCleaned(Semaphore semaphore, int expectedCleanups, 325 String msg) { 326 if (expectedCleanups < 0) { 327 // can't predict - anything is OK 328 whitebox.fullGC(); 329 int acquired = drain(semaphore); 330 System.out.printf(msg + " %d times\n", acquired); 331 return; 332 } 333 334 long max_cycles = expectedCleanups > 0 ? 10 : 3; 335 // wait for at least 1 permit 336 int permits = Math.max(1, expectedCleanups); 337 long cycle = 0; 338 for (; cycle < max_cycles; cycle++) { 339 // Force GC 340 whitebox.fullGC(); 341 342 try { 343 if (semaphore.tryAcquire(permits, 344 Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) { 345 int acquired = drain(semaphore) + permits; 346 System.out.printf(msg + " %d times in cycle %d\n", acquired, cycle); 347 Assert.assertEquals(acquired, expectedCleanups, msg); 348 return; 349 } 350 } catch (InterruptedException ie) { 351 // retry in outer loop 352 } 353 } 354 // cleanup has not been invoked for at least 'permits' # of times 355 int acquired = drain(semaphore); 356 System.out.printf(msg + " %d times\n", acquired); 357 Assert.assertEquals(acquired, expectedCleanups, msg); 358 } 359 360 // like semaphore.drain() but wait a while for a permit to be available 361 private static int drain(Semaphore semaphore) { 362 int acquired = 0; 363 try { 364 while (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) { 365 acquired++; 366 } 367 } catch (InterruptedException e) { 368 // ignore 369 } 370 return acquired; 371 } 372 373 /** 374 * Create a CleanableCase for a PhantomReference. 375 * @param cleaner the cleaner to use 376 * @param obj an object or null to create a new Object 377 * @return a new CleanableCase preset with the object, cleanup, and semaphore 378 */ 379 static CleanableCase setupPhantom(Cleaner cleaner, Object obj) { 380 if (obj == null) { 381 obj = new Object(); 382 } 383 Semaphore s1 = new Semaphore(0); 384 Cleaner.Cleanable c1 = cleaner.register(obj, s1::release); 385 386 return new CleanableCase(obj, c1, s1); 387 } 388 389 /** 390 * Create a CleanableCase for a PhantomReference. 391 * @param cleaner the cleaner to use 392 * @param obj an object or null to create a new Object 393 * @return a new CleanableCase preset with the object, cleanup, and semaphore 394 */ 395 static CleanableCase setupPhantomSubclass(Cleaner cleaner, Object obj) { 396 if (obj == null) { 397 obj = new Object(); 398 } 399 Semaphore s1 = new Semaphore(0); 400 401 Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) { 402 public void clean() { 403 s1.release(); 404 } 405 }; 406 407 return new CleanableCase(obj, c1, s1); 408 } 409 /** 410 * Create a CleanableCase for a WeakReference. 411 * @param cleaner the cleaner to use 412 * @param obj an object or null to create a new Object 413 * @return a new CleanableCase preset with the object, cleanup, and semaphore 414 */ 415 static CleanableCase setupWeakSubclass(Cleaner cleaner, Object obj) { 416 if (obj == null) { 417 obj = new Object(); 418 } 419 Semaphore s1 = new Semaphore(0); 420 421 Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) { 422 public void clean() { 423 s1.release(); 424 } 425 }; 426 427 return new CleanableCase(obj, c1, s1); 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 setupSoftSubclass(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<>(obj, cleaner) { 443 public void clean() { 444 s1.release(); 445 } 446 }; 447 448 return new CleanableCase(obj, c1, s1); 449 } 450 451 /** 452 * Create a CleanableCase for a PhantomReference. 453 * @param cleaner the cleaner to use 454 * @param obj an object or null to create a new Object 455 * @return a new CleanableCase preset with the object, cleanup, and semaphore 456 */ 457 static CleanableCase setupPhantomSubclassException(Cleaner cleaner, Object obj) { 458 if (obj == null) { 459 obj = new Object(); 460 } 461 Semaphore s1 = new Semaphore(0); 462 463 Cleaner.Cleanable c1 = new PhantomCleanable<>(obj, cleaner) { 464 public void clean() { 465 s1.release(); 466 throw new RuntimeException("Exception thrown to cleaner thread"); 467 } 468 }; 469 470 return new CleanableCase(obj, c1, s1, true); 471 } 472 473 /** 474 * Create a CleanableCase for a WeakReference. 475 * @param cleaner the cleaner to use 476 * @param obj an object or null to create a new Object 477 * @return a new CleanableCase preset with the object, cleanup, and semaphore 478 */ 479 static CleanableCase setupWeakSubclassException(Cleaner cleaner, Object obj) { 480 if (obj == null) { 481 obj = new Object(); 482 } 483 Semaphore s1 = new Semaphore(0); 484 485 Cleaner.Cleanable c1 = new WeakCleanable<>(obj, cleaner) { 486 public void clean() { 487 s1.release(); 488 throw new RuntimeException("Exception thrown to cleaner thread"); 489 } 490 }; 491 492 return new CleanableCase(obj, c1, s1, true); 493 } 494 495 /** 496 * Create a CleanableCase for a SoftReference. 497 * @param cleaner the cleaner to use 498 * @param obj an object or null to create a new Object 499 * @return a new CleanableCase preset with the object, cleanup, and semaphore 500 */ 501 static CleanableCase setupSoftSubclassException(Cleaner cleaner, Object obj) { 502 if (obj == null) { 503 obj = new Object(); 504 } 505 Semaphore s1 = new Semaphore(0); 506 507 Cleaner.Cleanable c1 = new SoftCleanable<>(obj, cleaner) { 508 public void clean() { 509 s1.release(); 510 throw new RuntimeException("Exception thrown to cleaner thread"); 511 } 512 }; 513 514 return new CleanableCase(obj, c1, s1, true); 515 } 516 517 /** 518 * CleanableCase encapsulates the objects used for a test. 519 * The reference to the object is not held directly, 520 * but in a Reference object that can be cleared. 521 * The semaphore is used to count whether the cleanup occurred. 522 * It can be awaited on to determine that the cleanup has occurred. 523 * It can be checked for non-zero to determine if it was 524 * invoked or if it was invoked twice (a bug). 525 */ 526 static class CleanableCase { 527 528 private volatile Object referent; 529 private volatile Cleaner.Cleanable cleanable; 530 private final Semaphore semaphore; 531 private final boolean throwsEx; 532 final String cleanableDescr; 533 final boolean isCleanableImplClass; 534 private final Event[] events; // Sequence of calls to clean, clear, etc. 535 private volatile int eventNdx; 536 537 enum Event { 538 UNKNOWN, CLEAR, CLEAN, RELEASE_REFERENT, RELEASE_CLEANABLE; 539 540 boolean isBeforeIn(Event e, Event[] events) { 541 return this.indexIn(events) < e.indexIn(events); 542 } 543 544 boolean isPresentIn(Event[] events) { 545 return this.indexIn(events) < events.length; 546 } 547 548 int countOccurrencesIn(Event[] events) { 549 int count = 0; 550 for (int i = 0; i < events.length && events[i] != null; i++) { 551 if (events[i] == this) { 552 count++; 553 } 554 } 555 return count; 556 } 557 558 private int indexIn(Event[] events) { 559 for (int i = 0; i < events.length && events[i] != null; i++) { 560 if (events[i] == this) { 561 return i; 562 } 563 } 564 return events.length; 565 } 566 } 567 568 CleanableCase(Object referent, Cleaner.Cleanable cleanable, 569 Semaphore semaphore) { 570 this(referent, cleanable, semaphore, false); 571 } 572 CleanableCase(Object referent, Cleaner.Cleanable cleanable, 573 Semaphore semaphore, 574 boolean throwsEx) { 575 this.referent = referent; 576 this.cleanable = cleanable; 577 this.semaphore = semaphore; 578 this.throwsEx = throwsEx; 579 this.isCleanableImplClass = isCleanableImplClass(cleanable.getClass()); 580 this.cleanableDescr = cleanable.getClass().isAnonymousClass() 581 ? cleanable.getClass().getSuperclass().getName() 582 : cleanable.getClass().getName(); 583 this.events = new Event[5]; 584 this.eventNdx = 0; 585 } 586 587 public Object getReferent() { 588 return referent; 589 } 590 591 public CleanableCase releaseReferent() { 592 if (referent != null) { 593 addEvent(Event.RELEASE_REFERENT); 594 referent = null; 595 } 596 return this; 597 } 598 599 public Cleaner.Cleanable getCleanable() { 600 return cleanable; 601 } 602 603 public void doClean() { 604 try { 605 addEvent(Event.CLEAN); 606 cleanable.clean(); 607 } catch (RuntimeException ex) { 608 if (!throwsEx) { 609 // unless it is known this case throws an exception, rethrow 610 throw ex; 611 } 612 } 613 } 614 615 public void doClear() { 616 addEvent(Event.CLEAR); 617 ((Reference) cleanable).clear(); 618 } 619 620 public void releaseCleanableIfImpl() { 621 if (isCleanableImplClass) { 622 // only the CleanerImpl.XxxCleanableImpl instances are guaranteed 623 // to be retained by CleanerImpl so we can only release them and 624 // still expect the Cleanable to be cleaned... 625 releaseCleanable(); 626 } 627 } 628 629 public void releaseCleanable() { 630 if (cleanable != null) { 631 // unconditionally release any Cleanable 632 addEvent(Event.RELEASE_CLEANABLE); 633 cleanable = null; 634 } 635 } 636 637 public Semaphore getSemaphore() { 638 return semaphore; 639 } 640 641 public boolean isCleaned() { 642 return semaphore.availablePermits() != 0; 643 } 644 645 private synchronized void addEvent(Event e) { 646 events[eventNdx++] = e; 647 } 648 649 /** 650 * Compute the number of expected cleanups to be triggered 651 * given the type of Cleanable implementation and the collected events. 652 * Return -1 if we can't predict the number. 653 */ 654 public synchronized int expectedCleanups() { 655 656 if (isCleanableImplClass) { // the CleanerImpl.XxxCleanableImpl class 657 658 // doesn't matter how many or what the order of events was as long 659 // as either the clean() method was called explicitly or 660 // the referent was released, we expect exactly one cleanup action 661 // otherwise none 662 return Event.CLEAN.isPresentIn(events) || 663 Event.RELEASE_REFERENT.isPresentIn(events) ? 1 : 0; 664 665 } else { // plain XxxCleanable subclass 666 667 // each time clean() is invoked explicitly, a cleanup action is 668 // performed (no at-most-once semantics here) 669 int explicitCleanups = Event.CLEAN.countOccurrencesIn(events); 670 671 // if we clear()-ed the referent before releasing it, 672 // then that's all we get 673 if (Event.CLEAR.isBeforeIn(Event.RELEASE_REFERENT, events)) { 674 return explicitCleanups; 675 } 676 677 // else only if Cleanable was not released and clear() was not 678 // called, can we expect any predictable behavior from plain 679 // XxxCleanable subclass... 680 if (!Event.RELEASE_CLEANABLE.isPresentIn(events) && 681 !Event.CLEAR.isPresentIn(events)) { 682 // we get one additional cleanup to all the explicit ones 683 // if referent was released... 684 return explicitCleanups + 685 (Event.RELEASE_REFERENT.isPresentIn(events)? 1 : 0); 686 } 687 688 // -1 means we can't predict 689 return -1; 690 } 691 } 692 693 public synchronized String eventsString() { 694 return Arrays.asList(Arrays.copyOf(events, eventNdx)).toString(); 695 } 696 697 public String toString() { 698 return String.format("Case: %s, expected cleanups: %s, events: %s", 699 cleanableDescr, expectedCleanups(), eventsString()); 700 } 701 } 702 703 704 /** 705 * Example using a Cleaner to remove WeakKey references from a Map. 706 */ 707 @Test 708 void testWeakKey() { 709 ConcurrentHashMap<WeakKey<String>, String> map = new ConcurrentHashMap<>(); 710 Cleaner cleaner = Cleaner.create(); 711 String key = new String("foo"); // ensure it is not interned 712 String data = "bar"; 713 714 WeakKey<String> k1 = new WeakKey<>(key, cleaner, map); 715 map.put(k1, data); 716 717 WeakKey<String> k2 = new WeakKey<>(key, cleaner, map); 718 719 Assert.assertEquals(map.get(k2), data, "value should be found in the map"); 720 // ensure key is reachable until the lookup above 721 Reference.reachabilityFence(key); 722 // make key unreachable now 723 key = null; 724 System.gc(); 725 Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map"); 726 727 final long CYCLE_MAX = Utils.adjustTimeout(30L); 728 for (int i = 1; map.size() > 0 && i < CYCLE_MAX; i++) { 729 map.forEach( (k, v) -> System.out.printf(" k: %s, v: %s%n", k, v)); 730 try { 731 Thread.sleep(10L); 732 } catch (InterruptedException ie) {} 733 } 734 Assert.assertEquals(map.size(), 0, "Expected map to be empty;"); 735 // ensure cleaner an k1 remain reachable until the end of the test 736 Reference.reachabilityFence(cleaner); 737 Reference.reachabilityFence(k1); 738 } 739 740 /** 741 * Test sample class for WeakKeys in Map. 742 * @param <K> A WeakKey of type K 743 */ 744 class WeakKey<K> extends WeakCleanable<K> { 745 private final int hash; 746 private final ConcurrentHashMap<WeakKey<K>, ?> map; 747 748 public WeakKey(K key, Cleaner c, ConcurrentHashMap<WeakKey<K>, ?> map) { 749 super(key, c); 750 this.hash = key.hashCode(); 751 this.map = map; 752 } 753 754 @Override 755 public void clean() { 756 map.remove(this); 757 } 758 759 public int hashCode() { return hash; } 760 761 public boolean equals(Object obj) { 762 if (obj == this) { 763 return true; 764 } 765 if (!(obj instanceof WeakKey)) return false; 766 K key = get(); 767 return key != null && key == ((WeakKey<?>)obj).get(); 768 } 769 770 public String toString() { 771 return "WeakKey:" + get(); 772 } 773 } 774 775 /** 776 * Verify that casting a Cleanup to a Reference is not allowed to 777 * get the referent or clear the reference. 778 */ 779 @Test 780 @SuppressWarnings("rawtypes") 781 void testReferentNotAvailable() { 782 Cleaner cleaner = Cleaner.create(); 783 Semaphore s1 = new Semaphore(0); 784 785 Object obj = new String("a new string"); 786 Cleaner.Cleanable c = cleaner.register(obj, () -> s1.release()); 787 Reference r = (Reference) c; 788 try { 789 Object o = r.get(); 790 System.out.printf("r: %s%n", Objects.toString(o)); 791 Assert.fail("should not be able to get the referent from Cleanable"); 792 } catch (UnsupportedOperationException uoe) { 793 // expected 794 } 795 796 try { 797 r.clear(); 798 Assert.fail("should not be able to clear the referent from Cleanable"); 799 } catch (UnsupportedOperationException uoe) { 800 // expected 801 } 802 803 // ensure obj is reachable until now 804 Reference.reachabilityFence(obj); 805 // make it unreachable now 806 obj = null; 807 checkCleaned(s1, 1, "reference was cleaned:"); 808 } 809 810 /** 811 * Test the Cleaner from the CleanerFactory. 812 */ 813 @Test 814 void testCleanerFactory() { 815 Cleaner cleaner = CleanerFactory.cleaner(); 816 817 CleanableCase s = setupPhantom(cleaner, null).releaseReferent(); 818 checkCleaned(s.getSemaphore(), 1, 819 "Object was cleaned using CleanerFactor.cleaner():"); 820 } 821 } |