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