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 }