001/*
002 * Copyright (C) 2008 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.internal;
016
017import static java.util.Objects.requireNonNull;
018
019import java.lang.ref.PhantomReference;
020import java.lang.ref.Reference;
021import java.lang.ref.ReferenceQueue;
022import java.lang.ref.WeakReference;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.Field;
025import java.lang.reflect.Method;
026import java.util.logging.Level;
027import java.util.logging.Logger;
028import org.jspecify.annotations.Nullable;
029
030/**
031 * Thread that finalizes referents. All references should implement {@code
032 * com.google.common.base.FinalizableReference}.
033 *
034 * <p>While this class is public, we consider it to be *internal* and not part of our published API.
035 * It is public so we can access it reflectively across class loaders in secure environments.
036 *
037 * <p>This class can't depend on other Guava code. If we were to load this class in the same class
038 * loader as the rest of Guava, this thread would keep an indirect strong reference to the class
039 * loader and prevent it from being garbage collected. This poses a problem for environments where
040 * you want to throw away the class loader. For example, dynamically reloading a web application or
041 * unloading an OSGi bundle.
042 *
043 * <p>{@code com.google.common.base.FinalizableReferenceQueue} loads this class in its own class
044 * loader. That way, this class doesn't prevent the main class loader from getting garbage
045 * collected, and this class can detect when the main class loader has been garbage collected and
046 * stop itself.
047 */
048public final class Finalizer implements Runnable {
049
050  private static final Logger logger = Logger.getLogger(Finalizer.class.getName());
051
052  /**
053   * Starts the Finalizer thread. FinalizableReferenceQueue calls this method reflectively.
054   *
055   * @param finalizableReferenceClass FinalizableReference.class.
056   * @param queue a reference queue that the thread will poll.
057   * @param frqReference a phantom reference to the FinalizableReferenceQueue, which will be queued
058   *     either when the FinalizableReferenceQueue is no longer referenced anywhere, or when its
059   *     close() method is called.
060   */
061  public static void startFinalizer(
062      Class<?> finalizableReferenceClass,
063      ReferenceQueue<Object> queue,
064      PhantomReference<Object> frqReference) {
065    /*
066     * We use finalizableReferenceClass for two things:
067     *
068     * 1) To invoke FinalizableReference.finalizeReferent()
069     *
070     * 2) To detect when FinalizableReference's class loader has to be garbage collected, at which
071     * point, Finalizer can stop running
072     */
073
074    Finalizer finalizer = new Finalizer(finalizableReferenceClass, queue, frqReference);
075    String threadName = "com.google.common.base.internal.Finalizer";
076    Thread thread = null;
077    if (bigThreadConstructor != null) {
078      try {
079        boolean inheritThreadLocals = false;
080        long defaultStackSize = 0;
081        thread =
082            bigThreadConstructor.newInstance(
083                (ThreadGroup) null, finalizer, threadName, defaultStackSize, inheritThreadLocals);
084      } catch (Throwable t) {
085        logger.log(
086            Level.INFO, "Failed to create a thread without inherited thread-local values", t);
087      }
088    }
089    if (thread == null) {
090      thread = new Thread((ThreadGroup) null, finalizer, threadName);
091    }
092    thread.setDaemon(true);
093
094    try {
095      if (inheritableThreadLocals != null) {
096        inheritableThreadLocals.set(thread, null);
097      }
098    } catch (Throwable t) {
099      logger.log(
100          Level.INFO,
101          "Failed to clear thread local values inherited by reference finalizer thread.",
102          t);
103    }
104
105    thread.start();
106  }
107
108  private final WeakReference<Class<?>> finalizableReferenceClassReference;
109  private final PhantomReference<Object> frqReference;
110  private final ReferenceQueue<Object> queue;
111
112  // By preference, we will use the Thread constructor that has an `inheritThreadLocals` parameter.
113  // But before Java 9, our only way not to inherit ThreadLocals is to zap them after the thread
114  // is created, by accessing a private field.
115  private static final @Nullable Constructor<Thread> bigThreadConstructor =
116      getBigThreadConstructor();
117
118  private static final @Nullable Field inheritableThreadLocals =
119      (bigThreadConstructor == null) ? getInheritableThreadLocalsField() : null;
120
121  /** Constructs a new finalizer thread. */
122  private Finalizer(
123      Class<?> finalizableReferenceClass,
124      ReferenceQueue<Object> queue,
125      PhantomReference<Object> frqReference) {
126    this.queue = queue;
127
128    this.finalizableReferenceClassReference = new WeakReference<>(finalizableReferenceClass);
129
130    // Keep track of the FRQ that started us so we know when to stop.
131    this.frqReference = frqReference;
132  }
133
134  /** Loops continuously, pulling references off the queue and cleaning them up. */
135  @SuppressWarnings("InfiniteLoopStatement")
136  @Override
137  public void run() {
138    while (true) {
139      try {
140        if (!cleanUp(queue.remove())) {
141          break;
142        }
143      } catch (InterruptedException e) {
144        // ignore
145      }
146    }
147  }
148
149  /**
150   * Cleans up the given reference and any other references already in the queue. Catches and logs
151   * all throwables.
152   *
153   * @return true if the caller should continue to wait for more references to be added to the
154   *     queue, false if the associated FinalizableReferenceQueue is no longer referenced.
155   */
156  private boolean cleanUp(Reference<?> firstReference) {
157    Method finalizeReferentMethod = getFinalizeReferentMethod();
158    if (finalizeReferentMethod == null) {
159      return false;
160    }
161
162    if (!finalizeReference(firstReference, finalizeReferentMethod)) {
163      return false;
164    }
165
166    /*
167     * Loop as long as we have references available so as not to waste CPU looking up the Method
168     * over and over again.
169     */
170    while (true) {
171      Reference<?> furtherReference = queue.poll();
172      if (furtherReference == null) {
173        return true;
174      }
175      if (!finalizeReference(furtherReference, finalizeReferentMethod)) {
176        return false;
177      }
178    }
179  }
180
181  /**
182   * Cleans up the given reference. Catches and logs all throwables.
183   *
184   * @return true if the caller should continue to clean up references from the queue, false if the
185   *     associated FinalizableReferenceQueue is no longer referenced.
186   */
187  private boolean finalizeReference(Reference<?> reference, Method finalizeReferentMethod) {
188    /*
189     * This is for the benefit of phantom references. Weak and soft references will have already
190     * been cleared by this point.
191     */
192    reference.clear();
193
194    if (reference == frqReference) {
195      /*
196       * The client no longer has a reference to the FinalizableReferenceQueue. We can stop.
197       */
198      return false;
199    }
200
201    try {
202      finalizeReferentMethod.invoke(reference);
203    } catch (Throwable t) {
204      logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
205    }
206    return true;
207  }
208
209  /** Looks up FinalizableReference.finalizeReferent() method. */
210  private @Nullable Method getFinalizeReferentMethod() {
211    Class<?> finalizableReferenceClass = finalizableReferenceClassReference.get();
212    if (finalizableReferenceClass == null) {
213      /*
214       * FinalizableReference's class loader was reclaimed. While there's a chance that other
215       * finalizable references could be enqueued subsequently (at which point the class loader
216       * would be resurrected by virtue of us having a strong reference to it), we should pretty
217       * much just shut down and make sure we don't keep it alive any longer than necessary.
218       */
219      return null;
220    }
221    try {
222      return finalizableReferenceClass.getMethod("finalizeReferent");
223    } catch (NoSuchMethodException e) {
224      throw new AssertionError(e);
225    }
226  }
227
228  private static @Nullable Field getInheritableThreadLocalsField() {
229    if (isAndroid()) {
230      /*
231       * We need not worry about class unloading under Android. Plus, this approach doesn't always
232       * work under Android.
233       *
234       * (Clearing inheritableThreadLocals could still be useful independent of class unloading, but
235       * that's a larger problem, which we don't attempt to solve anywhere else in Guava.)
236       */
237      return null;
238    }
239    try {
240      Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals");
241      inheritableThreadLocals.setAccessible(true);
242      return inheritableThreadLocals;
243    } catch (Throwable t) {
244      logger.log(
245          Level.INFO,
246          "Couldn't access Thread.inheritableThreadLocals. Reference finalizer threads will "
247              + "inherit thread local values.");
248      return null;
249    }
250  }
251
252  private static @Nullable Constructor<Thread> getBigThreadConstructor() {
253    try {
254      return Thread.class.getConstructor(
255          ThreadGroup.class, Runnable.class, String.class, long.class, boolean.class);
256    } catch (Throwable t) {
257      // Probably pre Java 9. We'll fall back to Thread.inheritableThreadLocals.
258      return null;
259    }
260  }
261
262  private static boolean isAndroid() {
263    return requireNonNull(System.getProperty("java.runtime.name", "")).contains("Android");
264  }
265}