View Javadoc
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  
17  package com.google.common.base;
18  
19  import com.google.common.annotations.VisibleForTesting;
20  
21  import java.io.Closeable;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.lang.ref.PhantomReference;
25  import java.lang.ref.Reference;
26  import java.lang.ref.ReferenceQueue;
27  import java.lang.reflect.Method;
28  import java.net.URL;
29  import java.net.URLClassLoader;
30  import java.util.logging.Level;
31  import 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   */
92  public 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 }