001/* 002 * Copyright (C) 2007 The Guava Authors 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 015package com.google.common.base; 016 017import com.google.common.annotations.GwtIncompatible; 018import com.google.common.annotations.VisibleForTesting; 019import java.io.Closeable; 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.lang.ref.PhantomReference; 023import java.lang.ref.Reference; 024import java.lang.ref.ReferenceQueue; 025import java.lang.reflect.Method; 026import java.net.URL; 027import java.net.URLClassLoader; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030import org.checkerframework.checker.nullness.compatqual.NullableDecl; 031 032/** 033 * A reference queue with an associated background thread that dequeues references and invokes 034 * {@link FinalizableReference#finalizeReferent()} on them. 035 * 036 * <p>Keep a strong reference to this object until all of the associated referents have been 037 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code 038 * finalizeReferent()} on the remaining references. 039 * 040 * <p>As an example of how this is used, imagine you have a class {@code MyServer} that creates a a 041 * {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the {@code 042 * ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without calling 043 * its {@code close} method. You <em>could</em> use a finalizer to accomplish this, but that has a 044 * number of well-known problems. Here is how you might use this class instead: 045 * 046 * <pre>{@code 047 * public class MyServer implements Closeable { 048 * private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue(); 049 * // You might also share this between several objects. 050 * 051 * private static final Set<Reference<?>> references = Sets.newConcurrentHashSet(); 052 * // This ensures that the FinalizablePhantomReference itself is not garbage-collected. 053 * 054 * private final ServerSocket serverSocket; 055 * 056 * private MyServer(...) { 057 * ... 058 * this.serverSocket = new ServerSocket(...); 059 * ... 060 * } 061 * 062 * public static MyServer create(...) { 063 * MyServer myServer = new MyServer(...); 064 * final ServerSocket serverSocket = myServer.serverSocket; 065 * Reference<?> reference = new FinalizablePhantomReference<MyServer>(myServer, frq) { 066 * public void finalizeReferent() { 067 * references.remove(this): 068 * if (!serverSocket.isClosed()) { 069 * ...log a message about how nobody called close()... 070 * try { 071 * serverSocket.close(); 072 * } catch (IOException e) { 073 * ... 074 * } 075 * } 076 * } 077 * }; 078 * references.add(reference); 079 * return myServer; 080 * } 081 * 082 * public void close() { 083 * serverSocket.close(); 084 * } 085 * } 086 * }</pre> 087 * 088 * @author Bob Lee 089 * @since 2.0 090 */ 091@GwtIncompatible 092public class FinalizableReferenceQueue implements Closeable { 093 /* 094 * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a 095 * map built by MapMaker) no longer has a strong reference to this object, the garbage collector 096 * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the 097 * Finalizer to stop. 098 * 099 * If this library is loaded in the system class loader, FinalizableReferenceQueue can load 100 * Finalizer directly with no problems. 101 * 102 * If this library is loaded in an application class loader, it's important that Finalizer not 103 * have a strong reference back to the class loader. Otherwise, you could have a graph like this: 104 * 105 * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader 106 * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance 107 * 108 * Even if no other references to classes from the application class loader remain, the Finalizer 109 * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the 110 * Finalizer running, and as a result, the application class loader can never be reclaimed. 111 * 112 * This means that dynamically loaded web applications and OSGi bundles can't be unloaded. 113 * 114 * If the library is loaded in an application class loader, we try to break the cycle by loading 115 * Finalizer in its own independent class loader: 116 * 117 * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue -> 118 * etc. -> Decoupled class loader -> Finalizer 119 * 120 * Now, Finalizer no longer keeps an indirect strong reference to the static 121 * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed 122 * at which point the Finalizer thread will stop and its decoupled class loader can also be 123 * reclaimed. 124 * 125 * If any of this fails along the way, we fall back to loading Finalizer directly in the 126 * application class loader. 127 * 128 * NOTE: The tests for this behavior (FinalizableReferenceQueueClassLoaderUnloadingTest) fail 129 * strangely when run in JDK 9. We are considering this a known issue. Please see 130 * https://github.com/google/guava/issues/3086 for more information. 131 */ 132 133 private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); 134 135 private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer"; 136 137 /** Reference to Finalizer.startFinalizer(). */ 138 private static final Method startFinalizer; 139 140 static { 141 Class<?> finalizer = 142 loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader()); 143 startFinalizer = getStartFinalizer(finalizer); 144 } 145 146 /** The actual reference queue that our background thread will poll. */ 147 final ReferenceQueue<Object> queue; 148 149 final PhantomReference<Object> frqRef; 150 151 /** Whether or not the background thread started successfully. */ 152 final boolean threadStarted; 153 154 /** Constructs a new queue. */ 155 public FinalizableReferenceQueue() { 156 // We could start the finalizer lazily, but I'd rather it blow up early. 157 queue = new ReferenceQueue<>(); 158 frqRef = new PhantomReference<Object>(this, queue); 159 boolean threadStarted = false; 160 try { 161 startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef); 162 threadStarted = true; 163 } catch (IllegalAccessException impossible) { 164 throw new AssertionError(impossible); // startFinalizer() is public 165 } catch (Throwable t) { 166 logger.log( 167 Level.INFO, 168 "Failed to start reference finalizer thread." 169 + " Reference cleanup will only occur when new references are created.", 170 t); 171 } 172 173 this.threadStarted = threadStarted; 174 } 175 176 @Override 177 public void close() { 178 frqRef.enqueue(); 179 cleanUp(); 180 } 181 182 /** 183 * Repeatedly dequeues references from the queue and invokes {@link 184 * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a 185 * no-op if the background thread was created successfully. 186 */ 187 void cleanUp() { 188 if (threadStarted) { 189 return; 190 } 191 192 Reference<?> reference; 193 while ((reference = queue.poll()) != null) { 194 /* 195 * This is for the benefit of phantom references. Weak and soft references will have already 196 * been cleared by this point. 197 */ 198 reference.clear(); 199 try { 200 ((FinalizableReference) reference).finalizeReferent(); 201 } catch (Throwable t) { 202 logger.log(Level.SEVERE, "Error cleaning up after reference.", t); 203 } 204 } 205 } 206 207 /** 208 * Iterates through the given loaders until it finds one that can load Finalizer. 209 * 210 * @return Finalizer.class 211 */ 212 private static Class<?> loadFinalizer(FinalizerLoader... loaders) { 213 for (FinalizerLoader loader : loaders) { 214 Class<?> finalizer = loader.loadFinalizer(); 215 if (finalizer != null) { 216 return finalizer; 217 } 218 } 219 220 throw new AssertionError(); 221 } 222 223 /** Loads Finalizer.class. */ 224 interface FinalizerLoader { 225 226 /** 227 * Returns Finalizer.class or null if this loader shouldn't or can't load it. 228 * 229 * @throws SecurityException if we don't have the appropriate privileges 230 */ 231 @NullableDecl 232 Class<?> loadFinalizer(); 233 } 234 235 /** 236 * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path, 237 * we needn't create a separate loader. 238 */ 239 static class SystemLoader implements FinalizerLoader { 240 // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable 241 // finding Finalizer on the system class path even if it is there. 242 @VisibleForTesting static boolean disabled; 243 244 @Override 245 @NullableDecl 246 public Class<?> loadFinalizer() { 247 if (disabled) { 248 return null; 249 } 250 ClassLoader systemLoader; 251 try { 252 systemLoader = ClassLoader.getSystemClassLoader(); 253 } catch (SecurityException e) { 254 logger.info("Not allowed to access system class loader."); 255 return null; 256 } 257 if (systemLoader != null) { 258 try { 259 return systemLoader.loadClass(FINALIZER_CLASS_NAME); 260 } catch (ClassNotFoundException e) { 261 // Ignore. Finalizer is simply in a child class loader. 262 return null; 263 } 264 } else { 265 return null; 266 } 267 } 268 } 269 270 /** 271 * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to 272 * our class loader (which could be that of a dynamically loaded web application or OSGi bundle), 273 * it would prevent our class loader from getting garbage collected. 274 */ 275 static class DecoupledLoader implements FinalizerLoader { 276 private static final String LOADING_ERROR = 277 "Could not load Finalizer in its own class loader. Loading Finalizer in the current class " 278 + "loader instead. As a result, you will not be able to garbage collect this class " 279 + "loader. To support reclaiming this class loader, either resolve the underlying " 280 + "issue, or move Guava to your system class path."; 281 282 @Override 283 @NullableDecl 284 public Class<?> loadFinalizer() { 285 try { 286 /* 287 * We use URLClassLoader because it's the only concrete class loader implementation in the 288 * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this 289 * class loader: 290 * 291 * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader 292 * 293 * System class loader will (and must) be the parent. 294 */ 295 ClassLoader finalizerLoader = newLoader(getBaseUrl()); 296 return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); 297 } catch (Exception e) { 298 logger.log(Level.WARNING, LOADING_ERROR, e); 299 return null; 300 } 301 } 302 303 /** Gets URL for base of path containing Finalizer.class. */ 304 URL getBaseUrl() throws IOException { 305 // Find URL pointing to Finalizer.class file. 306 String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; 307 URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); 308 if (finalizerUrl == null) { 309 throw new FileNotFoundException(finalizerPath); 310 } 311 312 // Find URL pointing to base of class path. 313 String urlString = finalizerUrl.toString(); 314 if (!urlString.endsWith(finalizerPath)) { 315 throw new IOException("Unsupported path style: " + urlString); 316 } 317 urlString = urlString.substring(0, urlString.length() - finalizerPath.length()); 318 return new URL(finalizerUrl, urlString); 319 } 320 321 /** Creates a class loader with the given base URL as its classpath. */ 322 URLClassLoader newLoader(URL base) { 323 // We use the bootstrap class loader as the parent because Finalizer by design uses 324 // only standard Java classes. That also means that FinalizableReferenceQueueTest 325 // doesn't pick up the wrong version of the Finalizer class. 326 return new URLClassLoader(new URL[] {base}, null); 327 } 328 } 329 330 /** 331 * Loads Finalizer directly using the current class loader. We won't be able to garbage collect 332 * this class loader, but at least the world doesn't end. 333 */ 334 static class DirectLoader implements FinalizerLoader { 335 @Override 336 public Class<?> loadFinalizer() { 337 try { 338 return Class.forName(FINALIZER_CLASS_NAME); 339 } catch (ClassNotFoundException e) { 340 throw new AssertionError(e); 341 } 342 } 343 } 344 345 /** Looks up Finalizer.startFinalizer(). */ 346 static Method getStartFinalizer(Class<?> finalizer) { 347 try { 348 return finalizer.getMethod( 349 "startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class); 350 } catch (NoSuchMethodException e) { 351 throw new AssertionError(e); 352 } 353 } 354}