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