1 /* 2 * Copyright (c) 2015, 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.Vector; 31 import java.util.concurrent.Semaphore; 32 import java.util.function.Consumer; 33 import java.util.function.Supplier; 34 35 import org.testng.Assert; 36 import org.testng.TestNG; 37 import org.testng.annotations.Test; 38 39 /* 40 * @test 41 * @library /lib/testlibrary 42 * @run testng/othervm -Xmx16m CleanerTest 43 */ 44 45 @Test 46 public class CleanerTest { 47 // A common CleaningService used by the test for notifications 48 static final Cleaner COMMON = Cleaner.create(); 49 50 /** 51 * Test that sequences of the various actions on a Reference 52 * and on the Cleanable instance have the desired result. 53 * The test cases are generated for each of phantom, weak and soft 54 * references. 55 * The sequence of actions includes all permutations to an initial 56 * list of actions including clearing the ref and resulting garbage 57 * collection actions on the reference, explicitly performing 58 * the cleanup and explicitly clearing the cleaning function. 59 */ 60 @Test 61 @SuppressWarnings("unchecked") 62 void test1() { 63 Cleaner cleaner = Cleaner.create(); 64 65 // Individually 66 generateCases(cleaner, c -> c.clearRef()); 67 generateCases(cleaner, c -> c.doClean()); 68 generateCases(cleaner, c -> c.doClear()); 69 70 // Pairs 71 generateCases(cleaner, c -> c.doClear(), c -> c.doClean()); 72 73 // Triplets 74 generateCases(cleaner, c -> c.doClear(), c -> c.doClean(), c -> c.clearRef()); 75 76 CleanableCase s = setupPhantom(COMMON, cleaner); 77 cleaner = null; 78 Assert.assertTrue(checkCleaned(s.getSemaphore()), "Cleaner cleanup should have occurred"); 79 80 } 81 82 /** 83 * Generate tests using the runnables for each of phantom, weak, 84 * and soft references. 85 * @param cleaner the cleaner 86 * @param runnables the sequence of actions on the test case 87 */ 88 @SuppressWarnings("unchecked") 89 void generateCases(Cleaner cleaner, Consumer<CleanableCase>... runnables) { 90 generateCases(() -> setupPhantom(cleaner, null), runnables.length, runnables); 91 generateCases(() -> setupWeak(cleaner, null), runnables.length, runnables); 92 generateCases(() -> setupSoft(cleaner, null), runnables.length, runnables); 93 } 94 95 /** 96 * Generate all permutations of the sequence of runnables 97 * and test each one. 98 * The permutations are generated using Heap, B.R. (1963) Permutations by Interchanges. 99 * @param generator the supplier of a CleanableCase 100 * @param n the first index to interchange 101 * @param runnables the sequence of actions 102 */ 103 @SuppressWarnings("unchecked") 104 void generateCases(Supplier<CleanableCase> generator, int n, Consumer<CleanableCase> ... runnables) { 105 System.out.printf("Heap n: %d%n", n); 106 if (n == 1) { 107 CleanableCase test = generator.get(); 108 // Apply the sequence of actions on the Ref 109 for (Consumer<CleanableCase> c : runnables) { 110 c.accept(test); 111 } 112 verify(test); 113 } else { 114 for (int i = 0; i < n - 1; i += 1) { 115 generateCases(generator, n - 1, runnables); 116 Consumer<CleanableCase> t = runnables[n - 1]; 117 int ndx = ((n & 1) == 0) ? i : 0; 118 runnables[n - 1] = runnables[ndx]; 119 runnables[ndx] = t; 120 } 121 generateCases(generator, n - 1, runnables); 122 } 123 } 124 125 /** 126 * Verify the test case. 127 * Any actions directly on the Reference or Cleanable have been executed. 128 * The CleanableCase under test is given a chance to do the cleanup 129 * by forcing a GC. 130 * The result is compared with the expecte result computed 131 * from the sequence of operations on the Cleanable. 132 * The Cleanable itself should have been cleanedup. 133 * 134 * @param test A CleanableCase containing the references 135 */ 136 void verify(CleanableCase test) { 137 int r = test.expectedResult(); 138 System.out.printf("Case: %s, expect: %s, events: %s%n", 139 test.getRef().getClass().getName(), test.eventName(r), test.eventsString()); 140 141 CleanableCase cc = setupPhantom(COMMON, test.getCleanable()); 142 test.clearCleanable(); // release this hard reference 143 144 boolean result = checkCleaned(test.getSemaphore()); 145 if (result) { 146 Assert.assertEquals(r, CleanableCase.EV_CLEAN, "expected cleaning to be done"); 147 } else { 148 Assert.assertNotEquals(r, CleanableCase.EV_CLEAN, "unexpected cleaning"); 149 } 150 Assert.assertTrue(checkCleaned(cc.getSemaphore()), "The reference to the Cleaner should have been freed"); 151 } 152 153 /** 154 * Test that releasing the reference to the Cleaner service allows it to be 155 * be freed. 156 */ 157 @Test 158 void test2() { 159 ReferenceQueue<Object> queue = new ReferenceQueue<>(); 160 Cleaner service = Cleaner.create(); 161 162 PhantomReference<Object> ref = new PhantomReference<>(service, queue); 163 System.gc(); 164 // Clear the Reference to the cleaning service and force a gc. 165 service = null; 166 System.gc(); 167 try { 168 Reference<?> r = queue.remove(); 169 Assert.assertEquals(r, ref, "Wrong Reference dequeued"); 170 } catch (InterruptedException ie) { 171 System.out.printf("queue.remove Interrupted%n"); 172 } 173 174 } 175 176 /** 177 * Check a set of semaphores having been released by cleanup handlers. 178 * Force a number of GC cycles to give the GC a chance to process 179 * all the References and for the cleanup functions to be run. 180 * 181 * @param semaphore a varargs list of Semaphores 182 * @return true if all of the semaphores have at least 1 permit, 183 * false otherwise. 184 */ 185 static boolean checkCleaned(Semaphore... semaphore) { 186 long[] cycles = new long[semaphore.length]; 187 long total = 0; 188 for (int cycle = 0; cycle < 20; cycle++) { 189 for (int i = 0; i < semaphore.length; i++) { 190 long count = semaphore[i].availablePermits(); 191 if (count > 0 && cycles[i] == 0) { 192 System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycle); 193 cycles[i] = cycle; 194 total += 1; 195 } 196 } 197 198 if (total == semaphore.length) { 199 System.out.printf(" All cleanups done in cycle: %d, total: %d%n", cycle, total); 200 for (int i = 0; i < semaphore.length; i++) { 201 long count = semaphore[i].availablePermits(); 202 Assert.assertEquals(count, 1, "Cleanable invoked more than once, semaphore " + i); 203 } 204 return true; // all references freed 205 } 206 // Force GC 207 memoryPressure(); 208 System.gc(); 209 } 210 // Not all objects have been cleaned 211 212 for (int i = 0; i < semaphore.length; i++) { 213 if (cycles[i] != 0) { 214 System.out.printf(" Cleanable[%d] cleaned in cycle: %d%n", i, cycles[i]); 215 } else { 216 System.out.printf(" Cleanable[%d] not cleaned%n", i); 217 } 218 } 219 220 return false; // Failing result 221 } 222 223 /** 224 * Create a CleanableCase for a PhantomReference. 225 * @param cleaner the cleaner to use 226 * @param obj an object or null to create a new Object 227 * @return a new CleanableCase preset with the object, cleanup, and semaphore 228 */ 229 static CleanableCase setupPhantom(Cleaner cleaner, Object obj) { 230 if (obj == null) { 231 obj = new Object(); 232 } 233 Semaphore s1 = new Semaphore(0); 234 Cleaner.Cleanable c1 = cleaner.phantomCleanable(obj, () -> s1.release()); 235 236 return new CleanableCase(new PhantomReference<>(obj, null), c1, s1); 237 } 238 239 /** 240 * Create a CleanableCase for a WeakReference. 241 * @param cleaner the cleaner to use 242 * @param obj an object or null to create a new Object 243 * @return a new CleanableCase preset with the object, cleanup, and semaphore 244 */ 245 static CleanableCase setupWeak(Cleaner cleaner, Object obj) { 246 if (obj == null) { 247 obj = new Object(); 248 } 249 Semaphore s1 = new Semaphore(0); 250 Cleaner.Cleanable c1 = cleaner.weakCleanable(obj, () -> s1.release()); 251 252 return new CleanableCase(new WeakReference<>(obj, null), c1, s1); 253 } 254 255 /** 256 * Create a CleanableCase for a SoftReference. 257 * @param cleaner the cleaner to use 258 * @param obj an object or null to create a new Object 259 * @return a new CleanableCase preset with the object, cleanup, and semaphore 260 */ 261 static CleanableCase setupSoft(Cleaner cleaner, Object obj) { 262 if (obj == null) { 263 obj = new Object(); 264 } 265 Semaphore s1 = new Semaphore(0); 266 Cleaner.Cleanable c1 = cleaner.softCleanable(obj, () -> s1.release()); 267 268 return new CleanableCase(new SoftReference<>(obj, null), c1, s1); 269 } 270 271 /** 272 * MemoryPressure allocates memory to force a gc and to clear SoftReferences. 273 */ 274 static void memoryPressure() { 275 Vector<Object> root = new Vector<>(); 276 try { 277 long free = 0; 278 while ((free = Runtime.getRuntime().freeMemory()) > 1_000_000) { 279 long[] extra = new long[1_000_000]; 280 root.addElement(extra); 281 } 282 } catch (OutOfMemoryError mem) { 283 // ignore 284 root = null; 285 } 286 } 287 288 /** 289 * CleanableCase encapsulates the objects used for a test. 290 * The reference to the object is not held directly, 291 * but in a Reference object that can be cleared. 292 * The semaphore is used to count whether the cleanup occurred. 293 * It can be awaited on to determine that the cleanup has occurred. 294 * It can be checked for non-zero to determine if it was 295 * invoked or if itwas invoked twice (a bug). 296 */ 297 static class CleanableCase { 298 299 private Reference<Object> ref; 300 private Cleaner.Cleanable cleanup; 301 private Semaphore semaphore; 302 private int[] events; // Sequence of calls to clean, clear, etc. 303 private int eventNdx; 304 public static int EV_UNKNOWN = 0; 305 public static int EV_CLEAR = 1; 306 public static int EV_CLEAN = 2; 307 public static int EV_UNREF = 3; 308 public static int EV_CLEAR_CLEANUP = 4; 309 310 311 CleanableCase(Reference<Object> ref, Cleaner.Cleanable cleanup, Semaphore semaphore) { 312 this.ref = ref; 313 this.cleanup = cleanup; 314 this.semaphore = semaphore; 315 this.events = new int[4]; 316 this.eventNdx = 0; 317 } 318 319 public Object getRef() { 320 return ref; 321 } 322 323 public void clearRef() { 324 addEvent(EV_UNREF); 325 ref.clear(); 326 } 327 328 public Cleaner.Cleanable getCleanable() { 329 return cleanup; 330 } 331 332 public void doClean() { 333 addEvent(EV_CLEAN); 334 cleanup.clean(); 335 } 336 337 public void doClear() { 338 addEvent(EV_CLEAR); 339 cleanup.clear(); 340 } 341 342 public void clearCleanable() { 343 addEvent(EV_CLEAR_CLEANUP); 344 cleanup = null; 345 } 346 347 public Semaphore getSemaphore() { 348 return semaphore; 349 } 350 351 public boolean isCleaned() { 352 return semaphore.availablePermits() != 0; 353 } 354 355 private synchronized void addEvent(int e) { 356 events[eventNdx++] = e; 357 } 358 359 /** 360 * Computed the expected result from the sequence of events. 361 * If EV_CLEAR appears before anything else, it is cleared. 362 * If EV_CLEAN appears before EV_UNREF, then it is cleaned. 363 * Anything else is Unknown. 364 * @return EV_CLEAR if the cleanup should occur; 365 * EV_CLEAN if the cleanup should occur; 366 * EV_UNKNOWN if it is unknown. 367 */ 368 public synchronized int expectedResult() { 369 // Test if EV_CLEAR appears before anything else 370 int clearNdx = indexOfEvent(EV_CLEAR); 371 int cleanNdx = indexOfEvent(EV_CLEAN); 372 int unrefNdx = indexOfEvent(EV_UNREF); 373 if (clearNdx < cleanNdx) { 374 return EV_CLEAR; 375 } 376 if (cleanNdx < clearNdx || cleanNdx < unrefNdx) { 377 return EV_CLEAN; 378 } 379 if (unrefNdx < eventNdx) { 380 return EV_CLEAN; 381 } 382 383 return EV_UNKNOWN; 384 } 385 386 private synchronized int indexOfEvent(int e) { 387 for (int i = 0; i < eventNdx; i++) { 388 if (events[i] == e) { 389 return i; 390 } 391 } 392 return eventNdx; 393 } 394 395 private static final String[] names = 396 {"UNKNOWN", "EV_CLEAR", "EV_CLEAN", "EV_UNREF", "EV_CLEAR_CLEANUP"}; 397 398 public String eventName(int event) { 399 return names[event]; 400 } 401 402 public synchronized String eventsString() { 403 StringBuilder sb = new StringBuilder(); 404 for (int i = 0; i < eventNdx; i++) { 405 sb.append(eventName(events[i])); 406 sb.append(' '); 407 } 408 return sb.toString(); 409 } 410 } 411 412 }