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 }