Home | History | Annotate | Download | only in base
      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 
     17 package com.google.common.base;
     18 
     19 import java.io.FileNotFoundException;
     20 import java.io.IOException;
     21 import java.lang.ref.Reference;
     22 import java.lang.ref.ReferenceQueue;
     23 import java.lang.reflect.Method;
     24 import java.net.URL;
     25 import java.net.URLClassLoader;
     26 import java.util.logging.Level;
     27 import 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  */
     40 public 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