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