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