blob: b45ec157879d408ef795ea399afbb6ab5b152211 [file] [log] [blame]
/*
* Copyright (C) 2008 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.base.internal;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
/**
* Thread that finalizes referents. All references should implement {@code
* com.google.common.base.FinalizableReference}.
*
* <p>While this class is public, we consider it to be *internal* and not part of our published API.
* It is public so we can access it reflectively across class loaders in secure environments.
*
* <p>This class can't depend on other Guava code. If we were to load this class in the same class
* loader as the rest of Guava, this thread would keep an indirect strong reference to the class
* loader and prevent it from being garbage collected. This poses a problem for environments where
* you want to throw away the class loader. For example, dynamically reloading a web application or
* unloading an OSGi bundle.
*
* <p>{@code com.google.common.base.FinalizableReferenceQueue} loads this class in its own class
* loader. That way, this class doesn't prevent the main class loader from getting garbage
* collected, and this class can detect when the main class loader has been garbage collected and
* stop itself.
*/
// no @ElementTypesAreNonNullByDefault for the reasons discussed above
public class Finalizer implements Runnable {
private static final Logger logger = Logger.getLogger(Finalizer.class.getName());
/** Name of FinalizableReference.class. */
private static final String FINALIZABLE_REFERENCE = "com.google.common.base.FinalizableReference";
/**
* Starts the Finalizer thread. FinalizableReferenceQueue calls this method reflectively.
*
* @param finalizableReferenceClass FinalizableReference.class.
* @param queue a reference queue that the thread will poll.
* @param frqReference a phantom reference to the FinalizableReferenceQueue, which will be queued
* either when the FinalizableReferenceQueue is no longer referenced anywhere, or when its
* close() method is called.
*/
public static void startFinalizer(
Class<?> finalizableReferenceClass,
ReferenceQueue<Object> queue,
PhantomReference<Object> frqReference) {
/*
* We use FinalizableReference.class for two things:
*
* 1) To invoke FinalizableReference.finalizeReferent()
*
* 2) To detect when FinalizableReference's class loader has to be garbage collected, at which
* point, Finalizer can stop running
*/
if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) {
throw new IllegalArgumentException("Expected " + FINALIZABLE_REFERENCE + ".");
}
Finalizer finalizer = new Finalizer(finalizableReferenceClass, queue, frqReference);
String threadName = Finalizer.class.getName();
Thread thread = null;
if (bigThreadConstructor != null) {
try {
boolean inheritThreadLocals = false;
long defaultStackSize = 0;
thread =
bigThreadConstructor.newInstance(
(ThreadGroup) null, finalizer, threadName, defaultStackSize, inheritThreadLocals);
} catch (Throwable t) {
logger.log(
Level.INFO, "Failed to create a thread without inherited thread-local values", t);
}
}
if (thread == null) {
thread = new Thread((ThreadGroup) null, finalizer, threadName);
}
thread.setDaemon(true);
try {
if (inheritableThreadLocals != null) {
inheritableThreadLocals.set(thread, null);
}
} catch (Throwable t) {
logger.log(
Level.INFO,
"Failed to clear thread local values inherited by reference finalizer thread.",
t);
}
thread.start();
}
private final WeakReference<Class<?>> finalizableReferenceClassReference;
private final PhantomReference<Object> frqReference;
private final ReferenceQueue<Object> queue;
// By preference, we will use the Thread constructor that has an `inheritThreadLocals` parameter.
// But before Java 9, our only way not to inherit ThreadLocals is to zap them after the thread
// is created, by accessing a private field.
@CheckForNull
private static final Constructor<Thread> bigThreadConstructor = getBigThreadConstructor();
@CheckForNull
private static final Field inheritableThreadLocals =
(bigThreadConstructor == null) ? getInheritableThreadLocalsField() : null;
/** Constructs a new finalizer thread. */
private Finalizer(
Class<?> finalizableReferenceClass,
ReferenceQueue<Object> queue,
PhantomReference<Object> frqReference) {
this.queue = queue;
this.finalizableReferenceClassReference = new WeakReference<>(finalizableReferenceClass);
// Keep track of the FRQ that started us so we know when to stop.
this.frqReference = frqReference;
}
/** Loops continuously, pulling references off the queue and cleaning them up. */
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
while (true) {
try {
if (!cleanUp(queue.remove())) {
break;
}
} catch (InterruptedException e) {
// ignore
}
}
}
/**
* Cleans up a single reference. Catches and logs all throwables.
*
* @return true if the caller should continue, false if the associated FinalizableReferenceQueue
* is no longer referenced.
*/
private boolean cleanUp(Reference<?> reference) {
Method finalizeReferentMethod = getFinalizeReferentMethod();
if (finalizeReferentMethod == null) {
return false;
}
do {
/*
* This is for the benefit of phantom references. Weak and soft references will have already
* been cleared by this point.
*/
reference.clear();
if (reference == frqReference) {
/*
* The client no longer has a reference to the FinalizableReferenceQueue. We can stop.
*/
return false;
}
try {
finalizeReferentMethod.invoke(reference);
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
}
/*
* Loop as long as we have references available so as not to waste CPU looking up the Method
* over and over again.
*/
} while ((reference = queue.poll()) != null);
return true;
}
/** Looks up FinalizableReference.finalizeReferent() method. */
@CheckForNull
private Method getFinalizeReferentMethod() {
Class<?> finalizableReferenceClass = finalizableReferenceClassReference.get();
if (finalizableReferenceClass == null) {
/*
* FinalizableReference's class loader was reclaimed. While there's a chance that other
* finalizable references could be enqueued subsequently (at which point the class loader
* would be resurrected by virtue of us having a strong reference to it), we should pretty
* much just shut down and make sure we don't keep it alive any longer than necessary.
*/
return null;
}
try {
return finalizableReferenceClass.getMethod("finalizeReferent");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
@CheckForNull
private static Field getInheritableThreadLocalsField() {
try {
Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocals.setAccessible(true);
return inheritableThreadLocals;
} catch (Throwable t) {
logger.log(
Level.INFO,
"Couldn't access Thread.inheritableThreadLocals. Reference finalizer threads will "
+ "inherit thread local values.");
return null;
}
}
@CheckForNull
private static Constructor<Thread> getBigThreadConstructor() {
try {
return Thread.class.getConstructor(
ThreadGroup.class, Runnable.class, String.class, long.class, boolean.class);
} catch (Throwable t) {
// Probably pre Java 9. We'll fall back to Thread.inheritableThreadLocals.
return null;
}
}
}