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