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 java.io.FileNotFoundException;
020 import java.io.IOException;
021 import java.lang.ref.Reference;
022 import java.lang.ref.ReferenceQueue;
023 import java.lang.reflect.Method;
024 import java.net.URL;
025 import java.net.URLClassLoader;
026 import java.util.logging.Level;
027 import java.util.logging.Logger;
028
029 /**
030 * A reference queue with an associated background thread that dequeues references and invokes
031 * {@link FinalizableReference#finalizeReferent()} on them.
032 *
033 * <p>Keep a strong reference to this object until all of the associated referents have been
034 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
035 * finalizeReferent()} on the remaining references.
036 *
037 * @author Bob Lee
038 * @since 2.0 (imported from Google Collections Library)
039 */
040 public class FinalizableReferenceQueue {
041 /*
042 * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
043 * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
044 * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
045 * Finalizer to stop.
046 *
047 * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
048 * Finalizer directly with no problems.
049 *
050 * If this library is loaded in an application class loader, it's important that Finalizer not
051 * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
052 *
053 * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
054 * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
055 *
056 * Even if no other references to classes from the application class loader remain, the Finalizer
057 * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
058 * Finalizer running, and as a result, the application class loader can never be reclaimed.
059 *
060 * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
061 *
062 * If the library is loaded in an application class loader, we try to break the cycle by loading
063 * Finalizer in its own independent class loader:
064 *
065 * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue
066 * -> etc. -> Decoupled class loader -> Finalizer
067 *
068 * Now, Finalizer no longer keeps an indirect strong reference to the static
069 * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
070 * at which point the Finalizer thread will stop and its decoupled class loader can also be
071 * reclaimed.
072 *
073 * If any of this fails along the way, we fall back to loading Finalizer directly in the
074 * application class loader.
075 */
076
077 private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
078
079 private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
080
081 /** Reference to Finalizer.startFinalizer(). */
082 private static final Method startFinalizer;
083 static {
084 Class<?> finalizer = loadFinalizer(
085 new SystemLoader(), new DecoupledLoader(), new DirectLoader());
086 startFinalizer = getStartFinalizer(finalizer);
087 }
088
089 /**
090 * The actual reference queue that our background thread will poll.
091 */
092 final ReferenceQueue<Object> queue;
093
094 /**
095 * Whether or not the background thread started successfully.
096 */
097 final boolean threadStarted;
098
099 /**
100 * Constructs a new queue.
101 */
102 @SuppressWarnings("unchecked")
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 */
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 */
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 */
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
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
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 */
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. */
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
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 */
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 }