001 /*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package com.google.common.base;
018
019 import com.google.common.annotations.VisibleForTesting;
020
021 import java.io.FileNotFoundException;
022 import java.io.IOException;
023 import java.lang.ref.Reference;
024 import java.lang.ref.ReferenceQueue;
025 import java.lang.reflect.Method;
026 import java.net.URL;
027 import java.net.URLClassLoader;
028 import java.util.logging.Level;
029 import java.util.logging.Logger;
030
031 /**
032 * A reference queue with an associated background thread that dequeues references and invokes
033 * {@link FinalizableReference#finalizeReferent()} on them.
034 *
035 * <p>Keep a strong reference to this object until all of the associated referents have been
036 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
037 * finalizeReferent()} on the remaining references.
038 *
039 * @author Bob Lee
040 * @since 2.0 (imported from Google Collections Library)
041 */
042 public class FinalizableReferenceQueue {
043 /*
044 * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
045 * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
046 * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
047 * Finalizer to stop.
048 *
049 * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
050 * Finalizer directly with no problems.
051 *
052 * If this library is loaded in an application class loader, it's important that Finalizer not
053 * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
054 *
055 * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
056 * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
057 *
058 * Even if no other references to classes from the application class loader remain, the Finalizer
059 * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
060 * Finalizer running, and as a result, the application class loader can never be reclaimed.
061 *
062 * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
063 *
064 * If the library is loaded in an application class loader, we try to break the cycle by loading
065 * Finalizer in its own independent class loader:
066 *
067 * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue
068 * -> etc. -> Decoupled class loader -> Finalizer
069 *
070 * Now, Finalizer no longer keeps an indirect strong reference to the static
071 * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
072 * at which point the Finalizer thread will stop and its decoupled class loader can also be
073 * reclaimed.
074 *
075 * If any of this fails along the way, we fall back to loading Finalizer directly in the
076 * application class loader.
077 */
078
079 private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
080
081 private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
082
083 /** Reference to Finalizer.startFinalizer(). */
084 private static final Method startFinalizer;
085 static {
086 Class<?> finalizer = loadFinalizer(
087 new SystemLoader(), new DecoupledLoader(), new DirectLoader());
088 startFinalizer = getStartFinalizer(finalizer);
089 }
090
091 /**
092 * The actual reference queue that our background thread will poll.
093 */
094 final ReferenceQueue<Object> queue;
095
096 /**
097 * Whether or not the background thread started successfully.
098 */
099 final boolean threadStarted;
100
101 /**
102 * Constructs a new queue.
103 */
104 @SuppressWarnings("unchecked")
105 public FinalizableReferenceQueue() {
106 // We could start the finalizer lazily, but I'd rather it blow up early.
107 ReferenceQueue<Object> queue;
108 boolean threadStarted = false;
109 try {
110 queue = (ReferenceQueue<Object>)
111 startFinalizer.invoke(null, FinalizableReference.class, this);
112 threadStarted = true;
113 } catch (IllegalAccessException impossible) {
114 throw new AssertionError(impossible); // startFinalizer() is public
115 } catch (Throwable t) {
116 logger.log(Level.INFO, "Failed to start reference finalizer thread."
117 + " Reference cleanup will only occur when new references are created.", t);
118 queue = new ReferenceQueue<Object>();
119 }
120
121 this.queue = queue;
122 this.threadStarted = threadStarted;
123 }
124
125 /**
126 * Repeatedly dequeues references from the queue and invokes {@link
127 * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a
128 * no-op if the background thread was created successfully.
129 */
130 void cleanUp() {
131 if (threadStarted) {
132 return;
133 }
134
135 Reference<?> reference;
136 while ((reference = queue.poll()) != null) {
137 /*
138 * This is for the benefit of phantom references. Weak and soft references will have already
139 * been cleared by this point.
140 */
141 reference.clear();
142 try {
143 ((FinalizableReference) reference).finalizeReferent();
144 } catch (Throwable t) {
145 logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
146 }
147 }
148 }
149
150 /**
151 * Iterates through the given loaders until it finds one that can load Finalizer.
152 *
153 * @return Finalizer.class
154 */
155 private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
156 for (FinalizerLoader loader : loaders) {
157 Class<?> finalizer = loader.loadFinalizer();
158 if (finalizer != null) {
159 return finalizer;
160 }
161 }
162
163 throw new AssertionError();
164 }
165
166 /**
167 * Loads Finalizer.class.
168 */
169 interface FinalizerLoader {
170
171 /**
172 * Returns Finalizer.class or null if this loader shouldn't or can't load it.
173 *
174 * @throws SecurityException if we don't have the appropriate privileges
175 */
176 Class<?> loadFinalizer();
177 }
178
179 /**
180 * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path,
181 * we needn't create a separate loader.
182 */
183 static class SystemLoader implements FinalizerLoader {
184 // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable
185 // finding Finalizer on the system class path even if it is there.
186 @VisibleForTesting
187 static boolean disabled;
188
189 @Override
190 public Class<?> loadFinalizer() {
191 if (disabled) {
192 return null;
193 }
194 ClassLoader systemLoader;
195 try {
196 systemLoader = ClassLoader.getSystemClassLoader();
197 } catch (SecurityException e) {
198 logger.info("Not allowed to access system class loader.");
199 return null;
200 }
201 if (systemLoader != null) {
202 try {
203 return systemLoader.loadClass(FINALIZER_CLASS_NAME);
204 } catch (ClassNotFoundException e) {
205 // Ignore. Finalizer is simply in a child class loader.
206 return null;
207 }
208 } else {
209 return null;
210 }
211 }
212 }
213
214 /**
215 * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to
216 * our class loader (which could be that of a dynamically loaded web application or OSGi bundle),
217 * it would prevent our class loader from getting garbage collected.
218 */
219 static class DecoupledLoader implements FinalizerLoader {
220 private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader."
221 + "Loading Finalizer in the current class loader instead. As a result, you will not be able"
222 + "to garbage collect this class loader. To support reclaiming this class loader, either"
223 + "resolve the underlying issue, or move Google Collections to your system class path.";
224
225 @Override
226 public Class<?> loadFinalizer() {
227 try {
228 /*
229 * We use URLClassLoader because it's the only concrete class loader implementation in the
230 * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this
231 * class loader:
232 *
233 * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader
234 *
235 * System class loader will (and must) be the parent.
236 */
237 ClassLoader finalizerLoader = newLoader(getBaseUrl());
238 return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
239 } catch (Exception e) {
240 logger.log(Level.WARNING, LOADING_ERROR, e);
241 return null;
242 }
243 }
244
245 /**
246 * Gets URL for base of path containing Finalizer.class.
247 */
248 URL getBaseUrl() throws IOException {
249 // Find URL pointing to Finalizer.class file.
250 String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
251 URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
252 if (finalizerUrl == null) {
253 throw new FileNotFoundException(finalizerPath);
254 }
255
256 // Find URL pointing to base of class path.
257 String urlString = finalizerUrl.toString();
258 if (!urlString.endsWith(finalizerPath)) {
259 throw new IOException("Unsupported path style: " + urlString);
260 }
261 urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
262 return new URL(finalizerUrl, urlString);
263 }
264
265 /** Creates a class loader with the given base URL as its classpath. */
266 URLClassLoader newLoader(URL base) {
267 // We use the bootstrap class loader as the parent because Finalizer by design uses
268 // only standard Java classes. That also means that FinalizableReferenceQueueTest
269 // doesn't pick up the wrong version of the Finalizer class.
270 return new URLClassLoader(new URL[] {base}, null);
271 }
272 }
273
274 /**
275 * Loads Finalizer directly using the current class loader. We won't be able to garbage collect
276 * this class loader, but at least the world doesn't end.
277 */
278 static class DirectLoader implements FinalizerLoader {
279 @Override
280 public Class<?> loadFinalizer() {
281 try {
282 return Class.forName(FINALIZER_CLASS_NAME);
283 } catch (ClassNotFoundException e) {
284 throw new AssertionError(e);
285 }
286 }
287 }
288
289 /**
290 * Looks up Finalizer.startFinalizer().
291 */
292 static Method getStartFinalizer(Class<?> finalizer) {
293 try {
294 return finalizer.getMethod("startFinalizer", Class.class, Object.class);
295 } catch (NoSuchMethodException e) {
296 throw new AssertionError(e);
297 }
298 }
299 }