001    /*
002     * Copyright (C) 2007 The Guava Authors
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package com.google.common.base;
018    
019    import java.io.FileNotFoundException;
020    import java.io.IOException;
021    import java.lang.ref.Reference;
022    import java.lang.ref.ReferenceQueue;
023    import java.lang.reflect.Method;
024    import java.net.URL;
025    import java.net.URLClassLoader;
026    import java.util.logging.Level;
027    import java.util.logging.Logger;
028    
029    /**
030     * A reference queue with an associated background thread that dequeues references and invokes
031     * {@link FinalizableReference#finalizeReferent()} on them.
032     *
033     * <p>Keep a strong reference to this object until all of the associated referents have been
034     * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
035     * finalizeReferent()} on the remaining references.
036     *
037     * @author Bob Lee
038     * @since 2.0 (imported from Google Collections Library)
039     */
040    public class FinalizableReferenceQueue {
041      /*
042       * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
043       * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
044       * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
045       * Finalizer to stop.
046       *
047       * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
048       * Finalizer directly with no problems.
049       *
050       * If this library is loaded in an application class loader, it's important that Finalizer not
051       * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
052       *
053       * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
054       * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
055       *
056       * Even if no other references to classes from the application class loader remain, the Finalizer
057       * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
058       * Finalizer running, and as a result, the application class loader can never be reclaimed.
059       *
060       * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
061       *
062       * If the library is loaded in an application class loader, we try to break the cycle by loading
063       * Finalizer in its own independent class loader:
064       *
065       * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue
066       * -> etc. -> Decoupled class loader -> Finalizer
067       *
068       * Now, Finalizer no longer keeps an indirect strong reference to the static
069       * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
070       * at which point the Finalizer thread will stop and its decoupled class loader can also be
071       * reclaimed.
072       *
073       * If any of this fails along the way, we fall back to loading Finalizer directly in the
074       * application class loader.
075       */
076    
077      private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
078    
079      private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
080    
081      /** Reference to Finalizer.startFinalizer(). */
082      private static final Method startFinalizer;
083      static {
084        Class<?> finalizer = loadFinalizer(
085            new SystemLoader(), new DecoupledLoader(), new DirectLoader());
086        startFinalizer = getStartFinalizer(finalizer);
087      }
088    
089      /**
090       * The actual reference queue that our background thread will poll.
091       */
092      final ReferenceQueue<Object> queue;
093    
094      /**
095       * Whether or not the background thread started successfully.
096       */
097      final boolean threadStarted;
098    
099      /**
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    }