FinalizableReferenceQueue.java revision 0888a09821a98ac0680fad765217302858e70fa4
1/*
2 * Copyright (C) 2007 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.base;
18
19import com.google.common.annotations.VisibleForTesting;
20
21import java.io.Closeable;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.lang.ref.PhantomReference;
25import java.lang.ref.Reference;
26import java.lang.ref.ReferenceQueue;
27import java.lang.reflect.Method;
28import java.net.URL;
29import java.net.URLClassLoader;
30import java.util.logging.Level;
31import java.util.logging.Logger;
32
33/**
34 * A reference queue with an associated background thread that dequeues references and invokes
35 * {@link FinalizableReference#finalizeReferent()} on them.
36 *
37 * <p>Keep a strong reference to this object until all of the associated referents have been
38 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
39 * finalizeReferent()} on the remaining references.
40 *
41 * <p>As an example of how this is used, imagine you have a class {@code MyServer} that creates a
42 * a {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the
43 * {@code ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without
44 * calling its {@code close} method. You <em>could</em> use a finalizer to accomplish this, but
45 * that has a number of well-known problems. Here is how you might use this class instead:
46 *
47 * <pre>
48 * public class MyServer implements Closeable {
49 *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
50 *   // You might also share this between several objects.
51 *
52 *   private static final Set&lt;Reference&lt;?>> references = Sets.newConcurrentHashSet();
53 *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
54 *
55 *   private final ServerSocket serverSocket;
56 *
57 *   private MyServer(...) {
58 *     ...
59 *     this.serverSocket = new ServerSocket(...);
60 *     ...
61 *   }
62 *
63 *   public static MyServer create(...) {
64 *     MyServer myServer = new MyServer(...);
65 *     final ServerSocket serverSocket = myServer.serverSocket;
66 *     Reference&lt;?> reference = new FinalizablePhantomReference&lt;MyServer>(myServer, frq) {
67 *       &#64;Override public void finalizeReferent() {
68 *         references.remove(this):
69 *         if (!serverSocket.isClosed()) {
70 *           ...log a message about how nobody called close()...
71 *           try {
72 *             serverSocket.close();
73 *           } catch (IOException e) {
74 *             ...
75 *           }
76 *         }
77 *       }
78 *     };
79 *     references.add(reference);
80 *     return myServer;
81 *   }
82 *
83 *   &#64;Override public void close() {
84 *     serverSocket.close();
85 *   }
86 * }
87 * </pre>
88 *
89 * @author Bob Lee
90 * @since 2.0 (imported from Google Collections Library)
91 */
92public class FinalizableReferenceQueue implements Closeable {
93  /*
94   * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
95   * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
96   * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
97   * Finalizer to stop.
98   *
99   * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
100   * Finalizer directly with no problems.
101   *
102   * If this library is loaded in an application class loader, it's important that Finalizer not
103   * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
104   *
105   * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
106   * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
107   *
108   * Even if no other references to classes from the application class loader remain, the Finalizer
109   * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
110   * Finalizer running, and as a result, the application class loader can never be reclaimed.
111   *
112   * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
113   *
114   * If the library is loaded in an application class loader, we try to break the cycle by loading
115   * Finalizer in its own independent class loader:
116   *
117   * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue
118   * -> etc. -> Decoupled class loader -> Finalizer
119   *
120   * Now, Finalizer no longer keeps an indirect strong reference to the static
121   * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
122   * at which point the Finalizer thread will stop and its decoupled class loader can also be
123   * reclaimed.
124   *
125   * If any of this fails along the way, we fall back to loading Finalizer directly in the
126   * application class loader.
127   */
128
129  private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
130
131  private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
132
133  /** Reference to Finalizer.startFinalizer(). */
134  private static final Method startFinalizer;
135  static {
136    Class<?> finalizer = loadFinalizer(
137        new SystemLoader(), new DecoupledLoader(), new DirectLoader());
138    startFinalizer = getStartFinalizer(finalizer);
139  }
140
141  /**
142   * The actual reference queue that our background thread will poll.
143   */
144  final ReferenceQueue<Object> queue;
145
146  final PhantomReference<Object> frqRef;
147
148  /**
149   * Whether or not the background thread started successfully.
150   */
151  final boolean threadStarted;
152
153  /**
154   * Constructs a new queue.
155   */
156  public FinalizableReferenceQueue() {
157    // We could start the finalizer lazily, but I'd rather it blow up early.
158    queue = new ReferenceQueue<Object>();
159    frqRef = new PhantomReference<Object>(this, queue);
160    boolean threadStarted = false;
161    try {
162      startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
163      threadStarted = true;
164    } catch (IllegalAccessException impossible) {
165      throw new AssertionError(impossible); // startFinalizer() is public
166    } catch (Throwable t) {
167      logger.log(Level.INFO, "Failed to start reference finalizer thread."
168          + " Reference cleanup will only occur when new references are created.", t);
169    }
170
171    this.threadStarted = threadStarted;
172  }
173
174  @Override
175  public void close() {
176    frqRef.enqueue();
177    cleanUp();
178  }
179
180  /**
181   * Repeatedly dequeues references from the queue and invokes {@link
182   * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a
183   * no-op if the background thread was created successfully.
184   */
185  void cleanUp() {
186    if (threadStarted) {
187      return;
188    }
189
190    Reference<?> reference;
191    while ((reference = queue.poll()) != null) {
192      /*
193       * This is for the benefit of phantom references. Weak and soft references will have already
194       * been cleared by this point.
195       */
196      reference.clear();
197      try {
198        ((FinalizableReference) reference).finalizeReferent();
199      } catch (Throwable t) {
200        logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
201      }
202    }
203  }
204
205  /**
206   * Iterates through the given loaders until it finds one that can load Finalizer.
207   *
208   * @return Finalizer.class
209   */
210  private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
211    for (FinalizerLoader loader : loaders) {
212      Class<?> finalizer = loader.loadFinalizer();
213      if (finalizer != null) {
214        return finalizer;
215      }
216    }
217
218    throw new AssertionError();
219  }
220
221  /**
222   * Loads Finalizer.class.
223   */
224  interface FinalizerLoader {
225
226    /**
227     * Returns Finalizer.class or null if this loader shouldn't or can't load it.
228     *
229     * @throws SecurityException if we don't have the appropriate privileges
230     */
231    Class<?> loadFinalizer();
232  }
233
234  /**
235   * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path,
236   * we needn't create a separate loader.
237   */
238  static class SystemLoader implements FinalizerLoader {
239    // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable
240    // finding Finalizer on the system class path even if it is there.
241    @VisibleForTesting
242    static boolean disabled;
243
244    @Override
245    public Class<?> loadFinalizer() {
246      if (disabled) {
247        return null;
248      }
249      ClassLoader systemLoader;
250      try {
251        systemLoader = ClassLoader.getSystemClassLoader();
252      } catch (SecurityException e) {
253        logger.info("Not allowed to access system class loader.");
254        return null;
255      }
256      if (systemLoader != null) {
257        try {
258          return systemLoader.loadClass(FINALIZER_CLASS_NAME);
259        } catch (ClassNotFoundException e) {
260          // Ignore. Finalizer is simply in a child class loader.
261          return null;
262        }
263      } else {
264        return null;
265      }
266    }
267  }
268
269  /**
270   * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to
271   * our class loader (which could be that of a dynamically loaded web application or OSGi bundle),
272   * it would prevent our class loader from getting garbage collected.
273   */
274  static class DecoupledLoader implements FinalizerLoader {
275    private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader."
276        + "Loading Finalizer in the current class loader instead. As a result, you will not be able"
277        + "to garbage collect this class loader. To support reclaiming this class loader, either"
278        + "resolve the underlying issue, or move Google Collections to your system class path.";
279
280    @Override
281    public Class<?> loadFinalizer() {
282      try {
283        /*
284         * We use URLClassLoader because it's the only concrete class loader implementation in the
285         * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this
286         * class loader:
287         *
288         * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader
289         *
290         * System class loader will (and must) be the parent.
291         */
292        ClassLoader finalizerLoader = newLoader(getBaseUrl());
293        return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
294      } catch (Exception e) {
295        logger.log(Level.WARNING, LOADING_ERROR, e);
296        return null;
297      }
298    }
299
300    /**
301     * Gets URL for base of path containing Finalizer.class.
302     */
303    URL getBaseUrl() throws IOException {
304      // Find URL pointing to Finalizer.class file.
305      String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
306      URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
307      if (finalizerUrl == null) {
308        throw new FileNotFoundException(finalizerPath);
309      }
310
311      // Find URL pointing to base of class path.
312      String urlString = finalizerUrl.toString();
313      if (!urlString.endsWith(finalizerPath)) {
314        throw new IOException("Unsupported path style: " + urlString);
315      }
316      urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
317      return new URL(finalizerUrl, urlString);
318    }
319
320    /** Creates a class loader with the given base URL as its classpath. */
321    URLClassLoader newLoader(URL base) {
322      // We use the bootstrap class loader as the parent because Finalizer by design uses
323      // only standard Java classes. That also means that FinalizableReferenceQueueTest
324      // doesn't pick up the wrong version of the Finalizer class.
325      return new URLClassLoader(new URL[] {base}, null);
326    }
327  }
328
329  /**
330   * Loads Finalizer directly using the current class loader. We won't be able to garbage collect
331   * this class loader, but at least the world doesn't end.
332   */
333  static class DirectLoader implements FinalizerLoader {
334    @Override
335    public Class<?> loadFinalizer() {
336      try {
337        return Class.forName(FINALIZER_CLASS_NAME);
338      } catch (ClassNotFoundException e) {
339        throw new AssertionError(e);
340      }
341    }
342  }
343
344  /**
345   * Looks up Finalizer.startFinalizer().
346   */
347  static Method getStartFinalizer(Class<?> finalizer) {
348    try {
349      return finalizer.getMethod(
350          "startFinalizer",
351          Class.class,
352          ReferenceQueue.class,
353          PhantomReference.class);
354    } catch (NoSuchMethodException e) {
355      throw new AssertionError(e);
356    }
357  }
358}
359