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.J2ktIncompatible;
019import com.google.common.annotations.VisibleForTesting;
020import java.io.Closeable;
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.lang.ref.PhantomReference;
024import java.lang.ref.Reference;
025import java.lang.ref.ReferenceQueue;
026import java.lang.reflect.Method;
027import java.net.URL;
028import java.net.URLClassLoader;
029import java.util.logging.Level;
030import java.util.logging.Logger;
031import javax.annotation.CheckForNull;
032
033/**
034 * A reference queue with an associated background thread that dequeues references and invokes
035 * {@link FinalizableReference#finalizeReferent()} on them. Java 9+ users should prefer {@link
036 * java.lang.ref.Cleaner Cleaner}; see example <a href="#cleaner">below</a>.
037 *
038 * <p>Keep a strong reference to this object until all of the associated referents have been
039 * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code
040 * finalizeReferent()} on the remaining references.
041 *
042 * <p>As an example of how this is used, imagine you have a class {@code MyServer} that creates a
043 * {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the {@code
044 * ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without calling
045 * its {@code close} method. You <em>could</em> use a finalizer to accomplish this, but that has a
046 * number of well-known problems. Here is how you might use this class instead:
047 *
048 * <pre>{@code
049 * public class MyServer implements Closeable {
050 *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
051 *   // You might also share this between several objects.
052 *
053 *   private static final Set<Reference<?>> references = Sets.newConcurrentHashSet();
054 *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
055 *
056 *   private final ServerSocket serverSocket;
057 *
058 *   private MyServer(...) {
059 *     ...
060 *     this.serverSocket = new ServerSocket(...);
061 *     ...
062 *   }
063 *
064 *   public static MyServer create(...) {
065 *     MyServer myServer = new MyServer(...);
066 *     ServerSocket serverSocket = myServer.serverSocket;
067 *     Reference<?> reference = new FinalizablePhantomReference<MyServer>(myServer, frq) {
068 *       @Override
069 *       public void finalizeReferent() {
070 *         references.remove(this):
071 *         if (!serverSocket.isClosed()) {
072 *           ...log a message about how nobody called close()...
073 *           try {
074 *             serverSocket.close();
075 *           } catch (IOException e) {
076 *             ...
077 *           }
078 *         }
079 *       }
080 *     };
081 *     references.add(reference);
082 *     return myServer;
083 *   }
084 *
085 *   @Override
086 *   public void close() throws IOException {
087 *     serverSocket.close();
088 *   }
089 * }
090 * }</pre>
091 *
092 * <p id="cleaner">Here is how you might achieve the same thing using {@link java.lang.ref.Cleaner
093 * Cleaner}, if you are using a Java version where that is available:
094 *
095 * <pre>{@code
096 * public class MyServer implements Closeable {
097 *   private static final Cleaner cleaner = Cleaner.create();
098 *   // You might also share this between several objects.
099 *
100 *   private final ServerSocket serverSocket;
101 *   private final Cleaner.Cleanable cleanable;
102 *
103 *   public MyServer(...) {
104 *     ...
105 *     this.serverSocket = new ServerSocket(...);
106 *     this.cleanable = cleaner.register(this, closeServerSocketRunnable(serverSocket));
107 *     ...
108 *   }
109 *
110 *   private static Runnable closeServerSocketRunnable(ServerSocket serverSocket) {
111 *     return () -> {
112 *       if (!serverSocket.isClosed()) {
113 *         ...log a message about how nobody called close()...
114 *         try {
115 *           serverSocket.close();
116 *         } catch (IOException e) {
117 *           ...
118 *         }
119 *       }
120 *     };
121 *   }
122 *
123 *   @Override
124 *   public void close() throws IOException {
125 *     serverSocket.close();
126 *     cleanable.clean();
127 *   }
128 * }
129 * }</pre>
130 *
131 * <p>Some care is needed when using {@code Cleaner} to ensure that the callback passed to {@code
132 * register} does not have a reference to the object (in this case, {@code MyServer}) that may be
133 * garbage-collected. That's why we are careful to make a {@code Runnable} that does not have a
134 * reference to any {@code MyServer} instance.
135 *
136 * @author Bob Lee
137 * @since 2.0
138 */
139@J2ktIncompatible
140@GwtIncompatible
141public class FinalizableReferenceQueue implements Closeable {
142  /*
143   * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a
144   * map built by MapMaker) no longer has a strong reference to this object, the garbage collector
145   * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the
146   * Finalizer to stop.
147   *
148   * If this library is loaded in the system class loader, FinalizableReferenceQueue can load
149   * Finalizer directly with no problems.
150   *
151   * If this library is loaded in an application class loader, it's important that Finalizer not
152   * have a strong reference back to the class loader. Otherwise, you could have a graph like this:
153   *
154   * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader
155   * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance
156   *
157   * Even if no other references to classes from the application class loader remain, the Finalizer
158   * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the
159   * Finalizer running, and as a result, the application class loader can never be reclaimed.
160   *
161   * This means that dynamically loaded web applications and OSGi bundles can't be unloaded.
162   *
163   * If the library is loaded in an application class loader, we try to break the cycle by loading
164   * Finalizer in its own independent class loader:
165   *
166   * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue ->
167   * etc. -> Decoupled class loader -> Finalizer
168   *
169   * Now, Finalizer no longer keeps an indirect strong reference to the static
170   * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed
171   * at which point the Finalizer thread will stop and its decoupled class loader can also be
172   * reclaimed.
173   *
174   * If any of this fails along the way, we fall back to loading Finalizer directly in the
175   * application class loader.
176   *
177   * NOTE: The tests for this behavior (FinalizableReferenceQueueClassLoaderUnloadingTest) fail
178   * strangely when run in JDK 9. We are considering this a known issue. Please see
179   * https://github.com/google/guava/issues/3086 for more information.
180   */
181
182  private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
183
184  private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
185
186  /** Reference to Finalizer.startFinalizer(). */
187  private static final Method startFinalizer;
188
189  static {
190    Class<?> finalizer =
191        loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader());
192    startFinalizer = getStartFinalizer(finalizer);
193  }
194
195  /** The actual reference queue that our background thread will poll. */
196  final ReferenceQueue<Object> queue;
197
198  final PhantomReference<Object> frqRef;
199
200  /** Whether or not the background thread started successfully. */
201  final boolean threadStarted;
202
203  /** Constructs a new queue. */
204  public FinalizableReferenceQueue() {
205    // We could start the finalizer lazily, but I'd rather it blow up early.
206    queue = new ReferenceQueue<>();
207    frqRef = new PhantomReference<>(this, queue);
208    boolean threadStarted = false;
209    try {
210      startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
211      threadStarted = true;
212    } catch (IllegalAccessException impossible) {
213      throw new AssertionError(impossible); // startFinalizer() is public
214    } catch (Throwable t) {
215      logger.log(
216          Level.INFO,
217          "Failed to start reference finalizer thread."
218              + " Reference cleanup will only occur when new references are created.",
219          t);
220    }
221
222    this.threadStarted = threadStarted;
223  }
224
225  @Override
226  public void close() {
227    frqRef.enqueue();
228    cleanUp();
229  }
230
231  /**
232   * Repeatedly dequeues references from the queue and invokes {@link
233   * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a
234   * no-op if the background thread was created successfully.
235   */
236  void cleanUp() {
237    if (threadStarted) {
238      return;
239    }
240
241    Reference<?> reference;
242    while ((reference = queue.poll()) != null) {
243      /*
244       * This is for the benefit of phantom references. Weak and soft references will have already
245       * been cleared by this point.
246       */
247      reference.clear();
248      try {
249        ((FinalizableReference) reference).finalizeReferent();
250      } catch (Throwable t) {
251        logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
252      }
253    }
254  }
255
256  /**
257   * Iterates through the given loaders until it finds one that can load Finalizer.
258   *
259   * @return Finalizer.class
260   */
261  private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
262    for (FinalizerLoader loader : loaders) {
263      Class<?> finalizer = loader.loadFinalizer();
264      if (finalizer != null) {
265        return finalizer;
266      }
267    }
268
269    throw new AssertionError();
270  }
271
272  /** Loads Finalizer.class. */
273  interface FinalizerLoader {
274
275    /**
276     * Returns Finalizer.class or null if this loader shouldn't or can't load it.
277     *
278     * @throws SecurityException if we don't have the appropriate privileges
279     */
280    @CheckForNull
281    Class<?> loadFinalizer();
282  }
283
284  /**
285   * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path,
286   * we needn't create a separate loader.
287   */
288  static class SystemLoader implements FinalizerLoader {
289    // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable
290    // finding Finalizer on the system class path even if it is there.
291    @VisibleForTesting static boolean disabled;
292
293    @Override
294    @CheckForNull
295    public Class<?> loadFinalizer() {
296      if (disabled) {
297        return null;
298      }
299      ClassLoader systemLoader;
300      try {
301        systemLoader = ClassLoader.getSystemClassLoader();
302      } catch (SecurityException e) {
303        logger.info("Not allowed to access system class loader.");
304        return null;
305      }
306      if (systemLoader != null) {
307        try {
308          return systemLoader.loadClass(FINALIZER_CLASS_NAME);
309        } catch (ClassNotFoundException e) {
310          // Ignore. Finalizer is simply in a child class loader.
311          return null;
312        }
313      } else {
314        return null;
315      }
316    }
317  }
318
319  /**
320   * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to
321   * our class loader (which could be that of a dynamically loaded web application or OSGi bundle),
322   * it would prevent our class loader from getting garbage collected.
323   */
324  static class DecoupledLoader implements FinalizerLoader {
325    private static final String LOADING_ERROR =
326        "Could not load Finalizer in its own class loader. Loading Finalizer in the current class "
327            + "loader instead. As a result, you will not be able to garbage collect this class "
328            + "loader. To support reclaiming this class loader, either resolve the underlying "
329            + "issue, or move Guava to your system class path.";
330
331    @Override
332    @CheckForNull
333    public Class<?> loadFinalizer() {
334      try {
335        /*
336         * We use URLClassLoader because it's the only concrete class loader implementation in the
337         * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this
338         * class loader:
339         *
340         * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader
341         *
342         * System class loader will (and must) be the parent.
343         */
344        ClassLoader finalizerLoader = newLoader(getBaseUrl());
345        return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
346      } catch (Exception e) {
347        logger.log(Level.WARNING, LOADING_ERROR, e);
348        return null;
349      }
350    }
351
352    /** Gets URL for base of path containing Finalizer.class. */
353    URL getBaseUrl() throws IOException {
354      // Find URL pointing to Finalizer.class file.
355      String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
356      URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
357      if (finalizerUrl == null) {
358        throw new FileNotFoundException(finalizerPath);
359      }
360
361      // Find URL pointing to base of class path.
362      String urlString = finalizerUrl.toString();
363      if (!urlString.endsWith(finalizerPath)) {
364        throw new IOException("Unsupported path style: " + urlString);
365      }
366      urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
367      return new URL(finalizerUrl, urlString);
368    }
369
370    /** Creates a class loader with the given base URL as its classpath. */
371    URLClassLoader newLoader(URL base) {
372      // We use the bootstrap class loader as the parent because Finalizer by design uses
373      // only standard Java classes. That also means that FinalizableReferenceQueueTest
374      // doesn't pick up the wrong version of the Finalizer class.
375      return new URLClassLoader(new URL[] {base}, null);
376    }
377  }
378
379  /**
380   * Loads Finalizer directly using the current class loader. We won't be able to garbage collect
381   * this class loader, but at least the world doesn't end.
382   */
383  static class DirectLoader implements FinalizerLoader {
384    @Override
385    public Class<?> loadFinalizer() {
386      try {
387        return Class.forName(FINALIZER_CLASS_NAME);
388      } catch (ClassNotFoundException e) {
389        throw new AssertionError(e);
390      }
391    }
392  }
393
394  /** Looks up Finalizer.startFinalizer(). */
395  static Method getStartFinalizer(Class<?> finalizer) {
396    try {
397      return finalizer.getMethod(
398          "startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class);
399    } catch (NoSuchMethodException e) {
400      throw new AssertionError(e);
401    }
402  }
403}