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 com.google.common.annotations.VisibleForTesting;
     20 
     21 import java.io.Closeable;
     22 import java.io.FileNotFoundException;
     23 import java.io.IOException;
     24 import java.lang.ref.PhantomReference;
     25 import java.lang.ref.Reference;
     26 import java.lang.ref.ReferenceQueue;
     27 import java.lang.reflect.Method;
     28 import java.net.URL;
     29 import java.net.URLClassLoader;
     30 import java.util.logging.Level;
     31 import java.util.logging.Logger;
     32 
     33 import javax.annotation.Nullable;
     34 
     35 /**
     36  * A reference queue with an associated background thread that dequeues references and invokes
     37  * {@link FinalizableReference#finalizeReferent()} on them.
     38  *
     39  * <p>Keep a strong reference to this object until all of the associated referents have been
     40  * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
     41  * finalizeReferent()} on the remaining references.
     42  *
     43  * <p>As an example of how this is used, imagine you have a class {@code MyServer} that creates a
     44  * a {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the
     45  * {@code ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without
     46  * calling its {@code close} method. You <em>could</em> use a finalizer to accomplish this, but
     47  * that has a number of well-known problems. Here is how you might use this class instead:
     48  *
     49  * <pre>
     50  * public class MyServer implements Closeable {
     51  *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
     52  *   // You might also share this between several objects.
     53  *
     54  *   private static final Set&lt;Reference&lt;?>> references = Sets.newConcurrentHashSet();
     55  *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
     56  *
     57  *   private final ServerSocket serverSocket;
     58  *
     59  *   private MyServer(...) {
     60  *     ...
     61  *     this.serverSocket = new ServerSocket(...);
     62  *     ...
     63  *   }
     64  *
     65  *   public static MyServer create(...) {
     66  *     MyServer myServer = new MyServer(...);
     67  *     final ServerSocket serverSocket = myServer.serverSocket;
     68  *     Reference&lt;?> reference = new FinalizablePhantomReference&lt;MyServer>(myServer, frq) {
     69  *       &#64;Override public void finalizeReferent() {
     70  *         references.remove(this):
     71  *         if (!serverSocket.isClosed()) {
     72  *           ...log a message about how nobody called close()...
     73  *           try {
     74  *             serverSocket.close();
     75  *           } catch (IOException e) {
     76  *             ...
     77  *           }
     78  *         }
     79  *       }
     80  *     };
     81  *     references.add(reference);
     82  *     return myServer;
     83  *   }
     84  *
     85  *   &#64;Override public void close() {
     86  *     serverSocket.close();
     87  *   }
     88  * }
     89  * </pre>
     90  *
     91  * @author Bob Lee
     92  * @since 2.0 (imported from Google Collections Library)
     93  */
     94 public class FinalizableReferenceQueue implements Closeable {
     95   /*
     96    * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
     97    * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
     98    * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
     99    * Finalizer to stop.
    100    *
    101    * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
    102    * Finalizer directly with no problems.
    103    *
    104    * If this library is loaded in an application class loader, it's important that Finalizer not
    105    * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
    106    *
    107    * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
    108    * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
    109    *
    110    * Even if no other references to classes from the application class loader remain, the Finalizer
    111    * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
    112    * Finalizer running, and as a result, the application class loader can never be reclaimed.
    113    *
    114    * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
    115    *
    116    * If the library is loaded in an application class loader, we try to break the cycle by loading
    117    * Finalizer in its own independent class loader:
    118    *
    119    * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue
    120    * -> etc. -> Decoupled class loader -> Finalizer
    121    *
    122    * Now, Finalizer no longer keeps an indirect strong reference to the static
    123    * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
    124    * at which point the Finalizer thread will stop and its decoupled class loader can also be
    125    * reclaimed.
    126    *
    127    * If any of this fails along the way, we fall back to loading Finalizer directly in the
    128    * application class loader.
    129    */
    130 
    131   private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
    132 
    133   private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
    134 
    135   /** Reference to Finalizer.startFinalizer(). */
    136   private static final Method startFinalizer;
    137   static {
    138     Class<?> finalizer = loadFinalizer(
    139         new SystemLoader(), new DecoupledLoader(), new DirectLoader());
    140     startFinalizer = getStartFinalizer(finalizer);
    141   }
    142 
    143   /**
    144    * The actual reference queue that our background thread will poll.
    145    */
    146   final ReferenceQueue<Object> queue;
    147 
    148   final PhantomReference<Object> frqRef;
    149 
    150   /**
    151    * Whether or not the background thread started successfully.
    152    */
    153   final boolean threadStarted;
    154 
    155   /**
    156    * Constructs a new queue.
    157    */
    158   public FinalizableReferenceQueue() {
    159     // We could start the finalizer lazily, but I'd rather it blow up early.
    160     queue = new ReferenceQueue<Object>();
    161     frqRef = new PhantomReference<Object>(this, queue);
    162     boolean threadStarted = false;
    163     try {
    164       startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
    165       threadStarted = true;
    166     } catch (IllegalAccessException impossible) {
    167       throw new AssertionError(impossible); // startFinalizer() is public
    168     } catch (Throwable t) {
    169       logger.log(Level.INFO, "Failed to start reference finalizer thread."
    170           + " Reference cleanup will only occur when new references are created.", 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   /**
    224    * Loads Finalizer.class.
    225    */
    226   interface FinalizerLoader {
    227 
    228     /**
    229      * Returns Finalizer.class or null if this loader shouldn't or can't load it.
    230      *
    231      * @throws SecurityException if we don't have the appropriate privileges
    232      */
    233     @Nullable
    234     Class<?> loadFinalizer();
    235   }
    236 
    237   /**
    238    * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path,
    239    * we needn't create a separate loader.
    240    */
    241   static class SystemLoader implements FinalizerLoader {
    242     // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable
    243     // finding Finalizer on the system class path even if it is there.
    244     @VisibleForTesting
    245     static boolean disabled;
    246 
    247     @Override
    248     public Class<?> loadFinalizer() {
    249       if (disabled) {
    250         return null;
    251       }
    252       ClassLoader systemLoader;
    253       try {
    254         systemLoader = ClassLoader.getSystemClassLoader();
    255       } catch (SecurityException e) {
    256         logger.info("Not allowed to access system class loader.");
    257         return null;
    258       }
    259       if (systemLoader != null) {
    260         try {
    261           return systemLoader.loadClass(FINALIZER_CLASS_NAME);
    262         } catch (ClassNotFoundException e) {
    263           // Ignore. Finalizer is simply in a child class loader.
    264           return null;
    265         }
    266       } else {
    267         return null;
    268       }
    269     }
    270   }
    271 
    272   /**
    273    * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to
    274    * our class loader (which could be that of a dynamically loaded web application or OSGi bundle),
    275    * it would prevent our class loader from getting garbage collected.
    276    */
    277   static class DecoupledLoader implements FinalizerLoader {
    278     private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader."
    279         + "Loading Finalizer in the current class loader instead. As a result, you will not be able"
    280         + "to garbage collect this class loader. To support reclaiming this class loader, either"
    281         + "resolve the underlying issue, or move Google Collections to your system class path.";
    282 
    283     @Override
    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     /**
    304      * Gets URL for base of path containing Finalizer.class.
    305      */
    306     URL getBaseUrl() throws IOException {
    307       // Find URL pointing to Finalizer.class file.
    308       String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
    309       URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
    310       if (finalizerUrl == null) {
    311         throw new FileNotFoundException(finalizerPath);
    312       }
    313 
    314       // Find URL pointing to base of class path.
    315       String urlString = finalizerUrl.toString();
    316       if (!urlString.endsWith(finalizerPath)) {
    317         throw new IOException("Unsupported path style: " + urlString);
    318       }
    319       urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
    320       return new URL(finalizerUrl, urlString);
    321     }
    322 
    323     /** Creates a class loader with the given base URL as its classpath. */
    324     URLClassLoader newLoader(URL base) {
    325       // We use the bootstrap class loader as the parent because Finalizer by design uses
    326       // only standard Java classes. That also means that FinalizableReferenceQueueTest
    327       // doesn't pick up the wrong version of the Finalizer class.
    328       return new URLClassLoader(new URL[] {base}, null);
    329     }
    330   }
    331 
    332   /**
    333    * Loads Finalizer directly using the current class loader. We won't be able to garbage collect
    334    * this class loader, but at least the world doesn't end.
    335    */
    336   static class DirectLoader implements FinalizerLoader {
    337     @Override
    338     public Class<?> loadFinalizer() {
    339       try {
    340         return Class.forName(FINALIZER_CLASS_NAME);
    341       } catch (ClassNotFoundException e) {
    342         throw new AssertionError(e);
    343       }
    344     }
    345   }
    346 
    347   /**
    348    * Looks up Finalizer.startFinalizer().
    349    */
    350   static Method getStartFinalizer(Class<?> finalizer) {
    351     try {
    352       return finalizer.getMethod(
    353           "startFinalizer",
    354           Class.class,
    355           ReferenceQueue.class,
    356           PhantomReference.class);
    357     } catch (NoSuchMethodException e) {
    358       throw new AssertionError(e);
    359     }
    360   }
    361 }
    362