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