1/* 2 * Copyright (C) 2007 The Guava Authors 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 17package com.google.common.base; 18 19import java.io.FileNotFoundException; 20import java.io.IOException; 21import java.lang.ref.Reference; 22import java.lang.ref.ReferenceQueue; 23import java.lang.reflect.Method; 24import java.net.URL; 25import java.net.URLClassLoader; 26import java.util.logging.Level; 27import java.util.logging.Logger; 28 29/** 30 * A reference queue with an associated background thread that dequeues references and invokes 31 * {@link FinalizableReference#finalizeReferent()} on them. 32 * 33 * <p>Keep a strong reference to this object until all of the associated referents have been 34 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code 35 * finalizeReferent()} on the remaining references. 36 * 37 * @author Bob Lee 38 * @since 2.0 (imported from Google Collections Library) 39 */ 40public class FinalizableReferenceQueue { 41 /* 42 * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a 43 * map built by MapMaker) no longer has a strong reference to this object, the garbage collector 44 * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the 45 * Finalizer to stop. 46 * 47 * If this library is loaded in the system class loader, FinalizableReferenceQueue can load 48 * Finalizer directly with no problems. 49 * 50 * If this library is loaded in an application class loader, it's important that Finalizer not 51 * have a strong reference back to the class loader. Otherwise, you could have a graph like this: 52 * 53 * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader 54 * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance 55 * 56 * Even if no other references to classes from the application class loader remain, the Finalizer 57 * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the 58 * Finalizer running, and as a result, the application class loader can never be reclaimed. 59 * 60 * This means that dynamically loaded web applications and OSGi bundles can't be unloaded. 61 * 62 * If the library is loaded in an application class loader, we try to break the cycle by loading 63 * Finalizer in its own independent class loader: 64 * 65 * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue 66 * -> etc. -> Decoupled class loader -> Finalizer 67 * 68 * Now, Finalizer no longer keeps an indirect strong reference to the static 69 * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed 70 * at which point the Finalizer thread will stop and its decoupled class loader can also be 71 * reclaimed. 72 * 73 * If any of this fails along the way, we fall back to loading Finalizer directly in the 74 * application class loader. 75 */ 76 77 private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); 78 79 private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer"; 80 81 /** Reference to Finalizer.startFinalizer(). */ 82 private static final Method startFinalizer; 83 static { 84 Class<?> finalizer = loadFinalizer( 85 new SystemLoader(), new DecoupledLoader(), new DirectLoader()); 86 startFinalizer = getStartFinalizer(finalizer); 87 } 88 89 /** 90 * The actual reference queue that our background thread will poll. 91 */ 92 final ReferenceQueue<Object> queue; 93 94 /** 95 * Whether or not the background thread started successfully. 96 */ 97 final boolean threadStarted; 98 99 /** 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} 287