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