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