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