001/* 002 * Copyright (C) 2011 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.testing; 018 019import static com.google.common.base.Throwables.throwIfUnchecked; 020import static com.google.common.collect.Sets.newConcurrentHashSet; 021import static java.lang.Math.max; 022import static java.util.concurrent.TimeUnit.SECONDS; 023 024import com.google.common.annotations.GwtIncompatible; 025import com.google.common.annotations.J2ktIncompatible; 026import com.google.common.base.FinalizablePhantomReference; 027import com.google.common.base.FinalizableReferenceQueue; 028import com.google.errorprone.annotations.DoNotMock; 029import com.google.errorprone.annotations.FormatMethod; 030import com.google.j2objc.annotations.J2ObjCIncompatible; 031import java.lang.ref.Reference; 032import java.lang.ref.WeakReference; 033import java.lang.reflect.InvocationTargetException; 034import java.lang.reflect.Method; 035import java.util.Locale; 036import java.util.Set; 037import java.util.concurrent.CancellationException; 038import java.util.concurrent.CountDownLatch; 039import java.util.concurrent.ExecutionException; 040import java.util.concurrent.Future; 041import java.util.concurrent.TimeoutException; 042import org.jspecify.annotations.NullMarked; 043import org.jspecify.annotations.Nullable; 044 045/** 046 * Testing utilities relating to garbage collection finalization. 047 * 048 * <p>Use this class to test code triggered by <em>finalization</em>, that is, one of the following 049 * actions taken by the java garbage collection system: 050 * 051 * <ul> 052 * <li>invoking the {@code finalize} methods of unreachable objects 053 * <li>clearing weak references to unreachable referents 054 * <li>enqueuing weak references to unreachable referents in their reference queue 055 * </ul> 056 * 057 * <p>This class uses (possibly repeated) invocations of {@link java.lang.System#gc()} to cause 058 * finalization to happen. However, a call to {@code System.gc()} is specified to be no more than a 059 * hint, so this technique may fail at the whim of the JDK implementation, for example if a user 060 * specified the JVM flag {@code -XX:+DisableExplicitGC}. But in practice, it works very well for 061 * ordinary tests. 062 * 063 * <p>Failure of the expected event to occur within an implementation-defined "reasonable" time 064 * period or an interrupt while waiting for the expected event will result in a {@link 065 * RuntimeException}. 066 * 067 * <p>Here's an example that tests a {@code finalize} method: 068 * 069 * {@snippet : 070 * final CountDownLatch latch = new CountDownLatch(1); 071 * Object x = new MyClass() { 072 * ... 073 * protected void finalize() { latch.countDown(); ... } 074 * }; 075 * x = null; // Hint to the JIT that x is stack-unreachable 076 * GcFinalization.await(latch); 077 * } 078 * 079 * <p>Here's an example that uses a user-defined finalization predicate: 080 * 081 * {@snippet : 082 * final WeakHashMap<Object, Object> map = new WeakHashMap<>(); 083 * map.put(new Object(), Boolean.TRUE); 084 * GcFinalization.awaitDone(new FinalizationPredicate() { 085 * public boolean isDone() { 086 * return map.isEmpty(); 087 * } 088 * }); 089 * } 090 * 091 * <p>Even if your non-test code does not use finalization, you can use this class to test for 092 * leaks, by ensuring that objects are no longer strongly referenced: 093 * 094 * {@snippet : 095 * // Helper function keeps victim stack-unreachable. 096 * private WeakReference<Foo> fooWeakRef() { 097 * Foo x = ....; 098 * WeakReference<Foo> weakRef = new WeakReference<>(x); 099 * // ... use x ... 100 * x = null; // Hint to the JIT that x is stack-unreachable 101 * return weakRef; 102 * } 103 * public void testFooLeak() { 104 * GcFinalization.awaitClear(fooWeakRef()); 105 * } 106 * } 107 * 108 * <p>This class cannot currently be used to test soft references, since this class does not try to 109 * create the memory pressure required to cause soft references to be cleared. 110 * 111 * <p>This class only provides testing utilities. It is not designed for direct use in production or 112 * for benchmarking. 113 * 114 * @author mike nonemacher 115 * @author Martin Buchholz 116 * @since 11.0 117 */ 118@GwtIncompatible 119@J2ktIncompatible 120@J2ObjCIncompatible // gc 121@NullMarked 122public final class GcFinalization { 123 private GcFinalization() {} 124 125 /** 126 * 10 seconds ought to be long enough for any object to be GC'ed and finalized. Unless we have a 127 * gigantic heap, in which case we scale by heap size. 128 */ 129 private static long timeoutSeconds() { 130 // This class can make no hard guarantees. The methods in this class are inherently flaky, but 131 // we try hard to make them robust in practice. We could additionally try to add in a system 132 // load timeout multiplier. Or we could try to use a CPU time bound instead of wall clock time 133 // bound. But these ideas are harder to implement. We do not try to detect or handle a 134 // user-specified -XX:+DisableExplicitGC. 135 // 136 // TODO(user): Consider using 137 // java/lang/management/OperatingSystemMXBean.html#getSystemLoadAverage() 138 // 139 // TODO(user): Consider scaling by number of mutator threads, 140 // e.g. using Thread#activeCount() 141 return max(10L, Runtime.getRuntime().totalMemory() / (32L * 1024L * 1024L)); 142 } 143 144 /** 145 * Waits until the given future {@linkplain Future#isDone is done}, invoking the garbage collector 146 * as necessary to try to ensure that this will happen. 147 * 148 * @throws RuntimeException if timed out or interrupted while waiting 149 */ 150 public static void awaitDone(Future<?> future) { 151 if (future.isDone()) { 152 return; 153 } 154 long timeoutSeconds = timeoutSeconds(); 155 long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); 156 do { 157 runFinalization(); 158 if (future.isDone()) { 159 return; 160 } 161 System.gc(); 162 try { 163 future.get(1L, SECONDS); 164 return; 165 } catch (CancellationException | ExecutionException ok) { 166 return; 167 } catch (InterruptedException ie) { 168 throw new RuntimeException("Unexpected interrupt while waiting for future", ie); 169 } catch (TimeoutException tryHarder) { 170 /* OK */ 171 } 172 } while (System.nanoTime() - deadline < 0); 173 throw formatRuntimeException("Future not done within %d second timeout", timeoutSeconds); 174 } 175 176 /** 177 * Waits until the given predicate returns true, invoking the garbage collector as necessary to 178 * try to ensure that this will happen. 179 * 180 * @throws RuntimeException if timed out or interrupted while waiting 181 */ 182 public static void awaitDone(FinalizationPredicate predicate) { 183 if (predicate.isDone()) { 184 return; 185 } 186 long timeoutSeconds = timeoutSeconds(); 187 long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); 188 do { 189 runFinalization(); 190 if (predicate.isDone()) { 191 return; 192 } 193 CountDownLatch done = new CountDownLatch(1); 194 createUnreachableLatchFinalizer(done); 195 await(done); 196 if (predicate.isDone()) { 197 return; 198 } 199 } while (System.nanoTime() - deadline < 0); 200 throw formatRuntimeException( 201 "Predicate did not become true within %d second timeout", timeoutSeconds); 202 } 203 204 /** 205 * Waits until the given latch has {@linkplain CountDownLatch#countDown counted down} to zero, 206 * invoking the garbage collector as necessary to try to ensure that this will happen. 207 * 208 * @throws RuntimeException if timed out or interrupted while waiting 209 */ 210 public static void await(CountDownLatch latch) { 211 if (latch.getCount() == 0) { 212 return; 213 } 214 long timeoutSeconds = timeoutSeconds(); 215 long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); 216 do { 217 runFinalization(); 218 if (latch.getCount() == 0) { 219 return; 220 } 221 System.gc(); 222 try { 223 if (latch.await(1L, SECONDS)) { 224 return; 225 } 226 } catch (InterruptedException ie) { 227 throw new RuntimeException("Unexpected interrupt while waiting for latch", ie); 228 } 229 } while (System.nanoTime() - deadline < 0); 230 throw formatRuntimeException( 231 "Latch failed to count down within %d second timeout", timeoutSeconds); 232 } 233 234 /** 235 * Creates a garbage object that counts down the latch in its finalizer. Sequestered into a 236 * separate method to make it somewhat more likely to be unreachable. 237 */ 238 private static void createUnreachableLatchFinalizer(CountDownLatch latch) { 239 FinalizableReference.register(new Object(), latch); 240 } 241 242 /** 243 * A predicate that is expected to return true subsequent to <em>finalization</em>, that is, one 244 * of the following actions taken by the garbage collector when performing a full collection in 245 * response to {@link System#gc()}: 246 * 247 * <ul> 248 * <li>invoking the {@code finalize} methods of unreachable objects 249 * <li>clearing weak references to unreachable referents 250 * <li>enqueuing weak references to unreachable referents in their reference queue 251 * </ul> 252 */ 253 @DoNotMock("Implement with a lambda") 254 public interface FinalizationPredicate { 255 /** Returns {@code true} if the finalization condition is met. */ 256 boolean isDone(); 257 } 258 259 /** 260 * Waits until the given weak reference is cleared, invoking the garbage collector as necessary to 261 * try to ensure that this will happen. 262 * 263 * <p>This is a convenience method, equivalent to: 264 * 265 * {@snippet : 266 * awaitDone(new FinalizationPredicate() { 267 * public boolean isDone() { 268 * return ref.get() == null; 269 * } 270 * }); 271 * } 272 * 273 * @throws RuntimeException if timed out or interrupted while waiting 274 */ 275 public static void awaitClear(WeakReference<?> ref) { 276 awaitDone(() -> ref.get() == null); 277 } 278 279 /** 280 * Tries to perform a "full" garbage collection cycle (including processing of weak references and 281 * invocation of finalize methods) and waits for it to complete. Ensures that at least one weak 282 * reference has been cleared and one {@code finalize} method has been run before this method 283 * returns. This method may be useful when testing the garbage collection mechanism itself, or 284 * inhibiting a spontaneous GC initiation in subsequent code. 285 * 286 * <p>In contrast, a plain call to {@link java.lang.System#gc()} does not ensure finalization 287 * processing and may run concurrently, for example, if the JVM flag {@code 288 * -XX:+ExplicitGCInvokesConcurrent} is used. 289 * 290 * <p>Whenever possible, it is preferable to test directly for some observable change resulting 291 * from GC, as with {@link #awaitClear}. Because there are no guarantees for the order of GC 292 * finalization processing, there may still be some unfinished work for the GC to do after this 293 * method returns. 294 * 295 * <p>This method does not create any memory pressure as would be required to cause soft 296 * references to be processed. 297 * 298 * @throws RuntimeException if timed out or interrupted while waiting 299 * @since 12.0 300 */ 301 public static void awaitFullGc() { 302 CountDownLatch finalizerRan = new CountDownLatch(1); 303 WeakReference<Object> ref = createWeakReferenceWithFinalizer(finalizerRan); 304 305 await(finalizerRan); 306 awaitClear(ref); 307 308 // Hope to catch some stragglers queued up behind our finalizable object 309 runFinalization(); 310 } 311 312 private static WeakReference<Object> createWeakReferenceWithFinalizer( 313 CountDownLatch finalizerRan) { 314 Object referent = new Object(); 315 FinalizableReference.register(referent, finalizerRan); 316 return new WeakReference<>(referent); 317 } 318 319 private static final class FinalizableReference extends FinalizablePhantomReference<Object> { 320 static final FinalizableReferenceQueue referenceQueue = new FinalizableReferenceQueue(); 321 static final Set<FinalizableReference> references = newConcurrentHashSet(); 322 323 static void register(Object referent, CountDownLatch latch) { 324 try { 325 references.add(new FinalizableReference(referent, latch)); 326 } finally { 327 GcFinalization.reachabilityFence(referent); 328 } 329 } 330 331 final CountDownLatch latch; 332 333 FinalizableReference(Object referent, CountDownLatch latch) { 334 super(referent, referenceQueue); 335 this.latch = latch; 336 } 337 338 @Override 339 public void finalizeReferent() { 340 references.remove(this); 341 latch.countDown(); 342 } 343 } 344 345 @FormatMethod 346 private static RuntimeException formatRuntimeException(String format, Object... args) { 347 return new RuntimeException(String.format(Locale.ROOT, format, args)); 348 } 349 350 private static final @Nullable Method runFinalizationMethod = getRunFinalizationMethod(); 351 352 private static @Nullable Method getRunFinalizationMethod() { 353 try { 354 return System.class.getMethod("runFinalization"); 355 } catch (NoSuchMethodException | SecurityException e) { 356 return null; 357 } 358 } 359 360 private static void runFinalization() { 361 if (runFinalizationMethod == null) { 362 return; 363 } 364 try { 365 runFinalizationMethod.invoke(null); 366 } catch (InvocationTargetException e) { 367 throwIfUnchecked(e.getCause()); 368 throw new AssertionError(e.getCause()); 369 } catch (IllegalAccessException e) { 370 throw new AssertionError(e); 371 } 372 } 373 374 // We call the method only after checking that it's present. 375 @IgnoreJRERequirement 376 @SuppressWarnings({ 377 "Java8ApiChecker", 378 "NewApi", 379 // This method is a helper, which we call from a `finally` block, as recommended. 380 "ReachabilityFenceUsage", 381 }) 382 static void reachabilityFence(@Nullable Object o) { 383 if (IS_REACHABILITY_FENCE_METHOD_USABLE) { 384 Reference.reachabilityFence(o); 385 } 386 } 387 388 private static final boolean IS_REACHABILITY_FENCE_METHOD_USABLE = 389 computeIsReachabilityFenceMethodUsable(); 390 391 private static boolean computeIsReachabilityFenceMethodUsable() { 392 try { 393 Method method = Reference.class.getMethod("reachabilityFence", Object.class); 394 method.invoke(null, GcFinalization.class); // to make sure the method is accessible 395 return true; 396 } catch (NoSuchMethodException | IllegalAccessException probablyBeforeJava9OrAndroid28) { 397 /* 398 * It's theoretically possible for Reference.reachabilityFence to exist under older VMs in an 399 * inaccessible form. 400 */ 401 return false; 402 } catch (InvocationTargetException e) { 403 /* 404 * It's theoretically possible for Reference.reachabilityFence to exist under older VMs but 405 * not work. (Under Android in particular, we really should check the API Level instead of 406 * probing for methods....) But it's hard to imagine how reachabilityFence in particular could 407 * exist but throw, so we propagate anything that's thrown, presumably an unchecked Exception 408 * or Error. 409 */ 410 throwIfUnchecked(e.getCause()); 411 throw new AssertionError(e.getCause()); 412 } 413 } 414}