< prev index next >

test/java/lang/ref/CleanerTest.java

Print this page




   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 }
< prev index next >