/* * Copyright (C) 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.droidfu.com.google.common.base; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.logging.Level; import java.util.logging.Logger; import com.github.droidfu.com.google.common.base.FinalizableReference; import com.github.droidfu.com.google.common.base.FinalizableReferenceQueue; /** * A reference queue with an associated background thread that dequeues * references and invokes {@link FinalizableReference#finalizeReferent()} on * them. * * <p>Keep a strong reference to this object until all of the associated * referents have been finalized. If this object is garbage collected earlier, * the backing thread will not invoke {@code finalizeReferent()} on the * remaining references. * * @author Bob Lee */ public class FinalizableReferenceQueue { /* * The Finalizer thread keeps a phantom reference to this object. When the * client (ReferenceMap, for example) no longer has a strong reference to * this object, the garbage collector will reclaim it and enqueue the * phantom reference. The enqueued reference will trigger the Finalizer to * stop. * * If this library is loaded in the system class loader, * FinalizableReferenceQueue can load Finalizer directly with no problems. * * If this library is loaded in an application class loader, it's important * that Finalizer not have a strong reference back to the class loader. * Otherwise, you could have a graph like this: * * Finalizer Thread * runs instance of -> Finalizer.class * loaded by -> Application class loader * which loaded -> ReferenceMap.class * which has a static -> FinalizableReferenceQueue instance * * Even if no other references to classes from the application class loader * remain, the Finalizer thread keeps an indirect strong reference to the * queue in ReferenceMap, which keeps the Finalizer running, and as a result, * the application class loader can never be reclaimed. * * This means that dynamically loaded web applications and OSGi bundles can't * be unloaded. * * If the library is loaded in an application class loader, we try to break * the cycle by loading Finalizer in its own independent class loader: * * System class loader * -> Application class loader * -> ReferenceMap * -> FinalizableReferenceQueue * -> etc. * -> Decoupled class loader * -> Finalizer * * Now, Finalizer no longer keeps an indirect strong reference to the * static FinalizableReferenceQueue field in ReferenceMap. The application * class loader can be reclaimed at which point the Finalizer thread will * stop and its decoupled class loader can also be reclaimed. * * If any of this fails along the way, we fall back to loading Finalizer * directly in the application class loader. */ private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer"; /** Reference to Finalizer.startFinalizer(). */ private static final Method startFinalizer; static { Class<?> finalizer = loadFinalizer( new SystemLoader(), new DecoupledLoader(), new DirectLoader()); startFinalizer = getStartFinalizer(finalizer); } /** * The actual reference queue that our background thread will poll. */ final ReferenceQueue<Object> queue; /** * Whether or not the background thread started successfully. */ final boolean threadStarted; /** * Constructs a new queue. */ @SuppressWarnings("unchecked") public FinalizableReferenceQueue() { // We could start the finalizer lazily, but I'd rather it blow up early. ReferenceQueue<Object> queue; boolean threadStarted = false; try { queue = (ReferenceQueue<Object>) startFinalizer.invoke(null, FinalizableReference.class, this); threadStarted = true; } catch (IllegalAccessException e) { // Finalizer.startFinalizer() is public. throw new AssertionError(e); } catch (Throwable t) { logger.log(Level.INFO, "Failed to start reference finalizer thread." + " Reference cleanup will only occur when new references are" + " created.", t); queue = new ReferenceQueue<Object>(); } this.queue = queue; this.threadStarted = threadStarted; } /** * Repeatedly dequeues references from the queue and invokes * {@link FinalizableReference#finalizeReferent()} on them until the queue * is empty. This method is a no-op if the background thread was created * successfully. */ void cleanUp() { if (threadStarted) { return; } Reference<?> reference; while ((reference = queue.poll()) != null) { /* * This is for the benefit of phantom references. Weak and soft * references will have already been cleared by this point. */ reference.clear(); try { ((FinalizableReference) reference).finalizeReferent(); } catch (Throwable t) { logger.log(Level.SEVERE, "Error cleaning up after reference.", t); } } } /** * Iterates through the given loaders until it finds one that can load * Finalizer. * * @return Finalizer.class */ private static Class<?> loadFinalizer(FinalizerLoader... loaders) { for (FinalizerLoader loader : loaders) { Class<?> finalizer = loader.loadFinalizer(); if (finalizer != null) { return finalizer; } } throw new AssertionError(); } /** * Loads Finalizer.class. */ interface FinalizerLoader { /** * Returns Finalizer.class or null if this loader shouldn't or can't load * it. * * @throws SecurityException if we don't have the appropriate privileges */ Class<?> loadFinalizer(); } /** * Tries to load Finalizer from the system class loader. If Finalizer is * in the system class path, we needn't create a separate loader. */ static class SystemLoader implements FinalizerLoader { public Class<?> loadFinalizer() { ClassLoader systemLoader; try { systemLoader = ClassLoader.getSystemClassLoader(); } catch (SecurityException e) { logger.info("Not allowed to access system class loader."); return null; } if (systemLoader != null) { try { return systemLoader.loadClass(FINALIZER_CLASS_NAME); } catch (ClassNotFoundException e) { // Ignore. Finalizer is simply in a child class loader. return null; } } else { return null; } } } /** * Try to load Finalizer in its own class loader. If Finalizer's thread * had a direct reference to our class loader (which could be that of * a dynamically loaded web application or OSGi bundle), it would prevent * our class loader from getting garbage collected. */ static class DecoupledLoader implements FinalizerLoader { private static final String LOADING_ERROR = "Could not load Finalizer in" + " its own class loader. Loading Finalizer in the current class loader" + " instead. As a result, you will not be able to garbage collect this" + " class loader. To support reclaiming this class loader, either" + " resolve the underlying issue, or move Google Collections to your" + " system class path."; public Class<?> loadFinalizer() { try { /* * We use URLClassLoader because it's the only concrete class loader * implementation in the JDK. If we used our own ClassLoader subclass, * Finalizer would indirectly reference this class loader: * * Finalizer.class -> * CustomClassLoader -> * CustomClassLoader.class -> * This class loader * * System class loader will (and must) be the parent. */ ClassLoader finalizerLoader = newLoader(getBaseUrl()); return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); } catch (Exception e) { logger.log(Level.WARNING, LOADING_ERROR, e); return null; } } /** * Gets URL for base of path containing Finalizer.class. */ URL getBaseUrl() throws IOException { // Find URL pointing to Finalizer.class file. String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); if (finalizerUrl == null) { throw new FileNotFoundException(finalizerPath); } // Find URL pointing to base of class path. String urlString = finalizerUrl.toString(); if (!urlString.endsWith(finalizerPath)) { throw new IOException("Unsupported path style: " + urlString); } urlString = urlString.substring(0, urlString.length() - finalizerPath.length()); return new URL(finalizerUrl, urlString); } /** Creates a class loader with the given base URL as its classpath. */ URLClassLoader newLoader(URL base) { return new URLClassLoader(new URL[] { base }); } } /** * Loads Finalizer directly using the current class loader. We won't be * able to garbage collect this class loader, but at least the world * doesn't end. */ static class DirectLoader implements FinalizerLoader { public Class<?> loadFinalizer() { try { return Class.forName(FINALIZER_CLASS_NAME); } catch (ClassNotFoundException e) { throw new AssertionError(e); } } } /** * Looks up Finalizer.startFinalizer(). */ static Method getStartFinalizer(Class<?> finalizer) { try { return finalizer.getMethod("startFinalizer", Class.class, Object.class); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } }