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