aboutsummaryrefslogtreecommitdiffstats
path: root/guava-testlib/src/com/google/common/testing/GcFinalization.java
blob: e541793d52e98d499bade5864e38bc3aa31a1851 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
 * Copyright (C) 2011 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.testing;

import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.annotations.Beta;

import java.lang.ref.WeakReference;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;

/**
 * Testing utilities relating to garbage collection finalization.
 *
 * <p>Use this class to test code triggered by <em>finalization</em>, that is, one of the
 * following actions taken by the java garbage collection system:
 *
 * <ul>
 * <li>invoking the {@code finalize} methods of unreachable objects
 * <li>clearing weak references to unreachable referents
 * <li>enqueuing weak references to unreachable referents in their reference queue
 * </ul>
 *
 * <p>This class uses (possibly repeated) invocations of {@link java.lang.System#gc()} to cause
 * finalization to happen.  However, a call to {@code System.gc()} is specified to be no more
 * than a hint, so this technique may fail at the whim of the JDK implementation, for example if
 * a user specified the JVM flag {@code -XX:+DisableExplicitGC}.  But in practice, it works very
 * well for ordinary tests.
 *
 * <p>Failure of the expected event to occur within an implementation-defined "reasonable" time
 * period or an interrupt while waiting for the expected event will result in a {@link
 * RuntimeException}.
 *
 * <p>Here's an example that tests a {@code finalize} method:
 *
 * <pre>   {@code
 *   final CountDownLatch latch = new CountDownLatch(1);
 *   Object x = new MyClass() {
 *     ...
 *     protected void finalize() { latch.countDown(); ... }
 *   };
 *   x = null;  // Hint to the JIT that x is unreachable
 *   GcFinalization.await(latch);
 * }</pre>
 *
 * <p>Here's an example that uses a user-defined finalization predicate:
 *
 * <pre>   {@code
 *   final WeakHashMap<Object, Object> map = new WeakHashMap<Object, Object>();
 *   map.put(new Object(), Boolean.TRUE);
 *   GcFinalization.awaitDone(new FinalizationPredicate() {
 *     public boolean isDone() {
 *       return map.isEmpty();
 *     }
 *   });
 * }</pre>
 *
 * <p>This class cannot currently be used to test soft references, since this class does not try to
 * create the memory pressure required to cause soft references to be cleared.
 *
 * <p>This class only provides testing utilities.  It is not designed for direct use in production
 * or for benchmarking.
 *
 * @author schmoe@google.com (mike nonemacher)
 * @author martinrb@google.com (Martin Buchholz)
 * @since 11.0
 */
@Beta
public final class GcFinalization {
  private GcFinalization() {}

  /**
   * 10 seconds ought to be long enough for any object to be GC'ed and finalized.  Unless we have a
   * gigantic heap, in which case we scale by heap size.
   */
  private static long timeoutSeconds() {
    // This class can make no hard guarantees.  The methods in this class are inherently flaky, but
    // we try hard to make them robust in practice.  We could additionally try to add in a system
    // load timeout multiplier.  Or we could try to use a CPU time bound instead of wall clock time
    // bound.  But these ideas are harder to implement.  We do not try to detect or handle a
    // user-specified -XX:+DisableExplicitGC.
    //
    // TODO(user): Consider using
    // java/lang/management/OperatingSystemMXBean.html#getSystemLoadAverage()
    //
    // TODO(user): Consider scaling by number of mutator threads,
    // e.g. using Thread#activeCount()
    return Math.max(10L, Runtime.getRuntime().totalMemory() / (32L * 1024L * 1024L));
  }

  /**
   * Waits until the given future {@linkplain Future#isDone is done}, invoking the garbage
   * collector as necessary to try to ensure that this will happen.
   *
   * @throws RuntimeException if timed out or interrupted while waiting
   */
  public static void awaitDone(Future<?> future) {
    if (future.isDone()) {
      return;
    }
    final long timeoutSeconds = timeoutSeconds();
    final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds);
    do {
      System.runFinalization();
      if (future.isDone()) {
        return;
      }
      System.gc();
      try {
        future.get(1L, SECONDS);
        return;
      } catch (CancellationException ok) {
        return;
      } catch (ExecutionException ok) {
        return;
      } catch (InterruptedException ie) {
        throw new RuntimeException("Unexpected interrupt while waiting for future", ie);
      } catch (TimeoutException tryHarder) {
        /* OK */
      }
    } while (System.nanoTime() - deadline < 0);
    throw new RuntimeException(
        String.format("Future not done within %d second timeout", timeoutSeconds));
  }

  /**
   * Waits until the given latch has {@linkplain CountDownLatch#countDown counted down} to zero,
   * invoking the garbage collector as necessary to try to ensure that this will happen.
   *
   * @throws RuntimeException if timed out or interrupted while waiting
   */
  public static void await(CountDownLatch latch) {
    if (latch.getCount() == 0) {
      return;
    }
    final long timeoutSeconds = timeoutSeconds();
    final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds);
    do {
      System.runFinalization();
      if (latch.getCount() == 0) {
        return;
      }
      System.gc();
      try {
        if (latch.await(1L, SECONDS)) {
          return;
        }
      } catch (InterruptedException ie) {
        throw new RuntimeException("Unexpected interrupt while waiting for latch", ie);
      }
    } while (System.nanoTime() - deadline < 0);
    throw new RuntimeException(
        String.format("Latch failed to count down within %d second timeout", timeoutSeconds));
  }

  /**
   * Creates a garbage object that counts down the latch in its finalizer.  Sequestered into a
   * separate method to make it somewhat more likely to be unreachable.
   */
  private static void createUnreachableLatchFinalizer(final CountDownLatch latch) {
    new Object() { @Override protected void finalize() { latch.countDown(); }};
  }

  /**
   * A predicate that is expected to return true subsequent to <em>finalization</em>, that is, one
   * of the following actions taken by the garbage collector when performing a full collection in
   * response to {@link System#gc()}:
   *
   * <ul>
   * <li>invoking the {@code finalize} methods of unreachable objects
   * <li>clearing weak references to unreachable referents
   * <li>enqueuing weak references to unreachable referents in their reference queue
   * </ul>
   */
  public interface FinalizationPredicate {
    boolean isDone();
  }

  /**
   * Waits until the given predicate returns true, invoking the garbage collector as necessary to
   * try to ensure that this will happen.
   *
   * @throws RuntimeException if timed out or interrupted while waiting
   */
  public static void awaitDone(FinalizationPredicate predicate) {
    if (predicate.isDone()) {
      return;
    }
    final long timeoutSeconds = timeoutSeconds();
    final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds);
    do {
      System.runFinalization();
      if (predicate.isDone()) {
        return;
      }
      CountDownLatch done = new CountDownLatch(1);
      createUnreachableLatchFinalizer(done);
      await(done);
      if (predicate.isDone()) {
        return;
      }
    } while (System.nanoTime() - deadline < 0);
    throw new RuntimeException(
        String.format("Predicate did not become true within %d second timeout", timeoutSeconds));
  }

  /**
   * Waits until the given weak reference is cleared, invoking the garbage collector as necessary
   * to try to ensure that this will happen.
   *
   * <p>This is a convenience method, equivalent to:
   * <pre>   {@code
   *   awaitDone(new FinalizationPredicate() {
   *     public boolean isDone() {
   *       return ref.get() == null;
   *     }
   *   });
   * }</pre>
   *
   * @throws RuntimeException if timed out or interrupted while waiting
   */
  public static void awaitClear(final WeakReference<?> ref) {
    awaitDone(new FinalizationPredicate() {
      public boolean isDone() {
        return ref.get() == null;
      }
    });
  }
}