001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.base;
016
017import com.google.common.annotations.GwtIncompatible;
018import com.google.common.annotations.VisibleForTesting;
019import java.io.Closeable;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.lang.ref.PhantomReference;
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;
030import org.checkerframework.checker.nullness.compatqual.NullableDecl;
031
032/**
033 * A reference queue with an associated background thread that dequeues references and invokes
034 * {@link FinalizableReference#finalizeReferent()} on them.
035 *
036 * <p>Keep a strong reference to this object until all of the associated referents have been
037 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
038 * finalizeReferent()} on the remaining references.
039 *
040 * <p>As an example of how this is used, imagine you have a class {@code MyServer} that creates a a
041 * {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the {@code
042 * ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without calling
043 * its {@code close} method. You <em>could</em> use a finalizer to accomplish this, but that has a
044 * number of well-known problems. Here is how you might use this class instead:
045 *
046 * <pre>{@code
047 * public class MyServer implements Closeable {
048 *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
049 *   // You might also share this between several objects.
050 *
051 *   private static final Set<Reference<?>> references = Sets.newConcurrentHashSet();
052 *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
053 *
054 *   private final ServerSocket serverSocket;
055 *
056 *   private MyServer(...) {
057 *     ...
058 *     this.serverSocket = new ServerSocket(...);
059 *     ...
060 *   }
061 *
062 *   public static MyServer create(...) {
063 *     MyServer myServer = new MyServer(...);
064 *     final ServerSocket serverSocket = myServer.serverSocket;
065 *     Reference<?> reference = new FinalizablePhantomReference<MyServer>(myServer, frq) {
066 *       public void finalizeReferent() {
067 *         references.remove(this):
068 *         if (!serverSocket.isClosed()) {
069 *           ...log a message about how nobody called close()...
070 *           try {
071 *             serverSocket.close();
072 *           } catch (IOException e) {
073 *             ...
074 *           }
075 *         }
076 *       }
077 *     };
078 *     references.add(reference);
079 *     return myServer;
080 *   }
081 *
082 *   public void close() {
083 *     serverSocket.close();
084 *   }
085 * }
086 * }</pre>
087 *
088 * @author Bob Lee
089 * @since 2.0
090 */
091@GwtIncompatible
092public class FinalizableReferenceQueue implements Closeable {
093  /*
094   * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
095   * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
096   * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
097   * Finalizer to stop.
098   *
099   * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
100   * Finalizer directly with no problems.
101   *
102   * If this library is loaded in an application class loader, it's important that Finalizer not
103   * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
104   *
105   * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
106   * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
107   *
108   * Even if no other references to classes from the application class loader remain, the Finalizer
109   * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
110   * Finalizer running, and as a result, the application class loader can never be reclaimed.
111   *
112   * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
113   *
114   * If the library is loaded in an application class loader, we try to break the cycle by loading
115   * Finalizer in its own independent class loader:
116   *
117   * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue ->
118   * etc. -> Decoupled class loader -> Finalizer
119   *
120   * Now, Finalizer no longer keeps an indirect strong reference to the static
121   * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
122   * at which point the Finalizer thread will stop and its decoupled class loader can also be
123   * reclaimed.
124   *
125   * If any of this fails along the way, we fall back to loading Finalizer directly in the
126   * application class loader.
127   *
128   * NOTE: The tests for this behavior (FinalizableReferenceQueueClassLoaderUnloadingTest) fail
129   * strangely when run in JDK 9. We are considering this a known issue. Please see
130   * https://github.com/google/guava/issues/3086 for more information.
131   */
132
133  private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
134
135  private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
136
137  /** Reference to Finalizer.startFinalizer(). */
138  private static final Method startFinalizer;
139
140  static {
141    Class<?> finalizer =
142        loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader());
143    startFinalizer = getStartFinalizer(finalizer);
144  }
145
146  /** The actual reference queue that our background thread will poll. */
147  final ReferenceQueue<Object> queue;
148
149  final PhantomReference<Object> frqRef;
150
151  /** Whether or not the background thread started successfully. */
152  final boolean threadStarted;
153
154  /** Constructs a new queue. */
155  public FinalizableReferenceQueue() {
156    // We could start the finalizer lazily, but I'd rather it blow up early.
157    queue = new ReferenceQueue<>();
158    frqRef = new PhantomReference<Object>(this, queue);
159    boolean threadStarted = false;
160    try {
161      startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
162      threadStarted = true;
163    } catch (IllegalAccessException impossible) {
164      throw new AssertionError(impossible); // startFinalizer() is public
165    } catch (Throwable t) {
166      logger.log(
167          Level.INFO,
168          "Failed to start reference finalizer thread."
169              + " Reference cleanup will only occur when new references are created.",
170          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  /** Loads Finalizer.class. */
224  interface FinalizerLoader {
225
226    /**
227     * Returns Finalizer.class or null if this loader shouldn't or can't load it.
228     *
229     * @throws SecurityException if we don't have the appropriate privileges
230     */
231    @NullableDecl
232    Class<?> loadFinalizer();
233  }
234
235  /**
236   * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path,
237   * we needn't create a separate loader.
238   */
239  static class SystemLoader implements FinalizerLoader {
240    // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable
241    // finding Finalizer on the system class path even if it is there.
242    @VisibleForTesting static boolean disabled;
243
244    @Override
245    @NullableDecl
246    public Class<?> loadFinalizer() {
247      if (disabled) {
248        return null;
249      }
250      ClassLoader systemLoader;
251      try {
252        systemLoader = ClassLoader.getSystemClassLoader();
253      } catch (SecurityException e) {
254        logger.info("Not allowed to access system class loader.");
255        return null;
256      }
257      if (systemLoader != null) {
258        try {
259          return systemLoader.loadClass(FINALIZER_CLASS_NAME);
260        } catch (ClassNotFoundException e) {
261          // Ignore. Finalizer is simply in a child class loader.
262          return null;
263        }
264      } else {
265        return null;
266      }
267    }
268  }
269
270  /**
271   * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to
272   * our class loader (which could be that of a dynamically loaded web application or OSGi bundle),
273   * it would prevent our class loader from getting garbage collected.
274   */
275  static class DecoupledLoader implements FinalizerLoader {
276    private static final String LOADING_ERROR =
277        "Could not load Finalizer in its own class loader. Loading Finalizer in the current class "
278            + "loader instead. As a result, you will not be able to garbage collect this class "
279            + "loader. To support reclaiming this class loader, either resolve the underlying "
280            + "issue, or move Guava to your system class path.";
281
282    @Override
283    @NullableDecl
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    /** Gets URL for base of path containing Finalizer.class. */
304    URL getBaseUrl() throws IOException {
305      // Find URL pointing to Finalizer.class file.
306      String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
307      URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
308      if (finalizerUrl == null) {
309        throw new FileNotFoundException(finalizerPath);
310      }
311
312      // Find URL pointing to base of class path.
313      String urlString = finalizerUrl.toString();
314      if (!urlString.endsWith(finalizerPath)) {
315        throw new IOException("Unsupported path style: " + urlString);
316      }
317      urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
318      return new URL(finalizerUrl, urlString);
319    }
320
321    /** Creates a class loader with the given base URL as its classpath. */
322    URLClassLoader newLoader(URL base) {
323      // We use the bootstrap class loader as the parent because Finalizer by design uses
324      // only standard Java classes. That also means that FinalizableReferenceQueueTest
325      // doesn't pick up the wrong version of the Finalizer class.
326      return new URLClassLoader(new URL[] {base}, null);
327    }
328  }
329
330  /**
331   * Loads Finalizer directly using the current class loader. We won't be able to garbage collect
332   * this class loader, but at least the world doesn't end.
333   */
334  static class DirectLoader implements FinalizerLoader {
335    @Override
336    public Class<?> loadFinalizer() {
337      try {
338        return Class.forName(FINALIZER_CLASS_NAME);
339      } catch (ClassNotFoundException e) {
340        throw new AssertionError(e);
341      }
342    }
343  }
344
345  /** Looks up Finalizer.startFinalizer(). */
346  static Method getStartFinalizer(Class<?> finalizer) {
347    try {
348      return finalizer.getMethod(
349          "startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class);
350    } catch (NoSuchMethodException e) {
351      throw new AssertionError(e);
352    }
353  }
354}