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