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