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