372 lines
12 KiB
Java
372 lines
12 KiB
Java
|
/*
|
||
|
* Copyright (C) 2007 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;
|
||
|
|
||
|
import java.io.Closeable;
|
||
|
import java.io.FileNotFoundException;
|
||
|
import java.io.IOException;
|
||
|
import java.lang.ref.PhantomReference;
|
||
|
import java.lang.ref.Reference;
|
||
|
import java.lang.ref.ReferenceQueue;
|
||
|
import java.lang.reflect.Method;
|
||
|
import java.net.URL;
|
||
|
import java.net.URLClassLoader;
|
||
|
import java.util.logging.Level;
|
||
|
import java.util.logging.Logger;
|
||
|
|
||
|
import com.google.common.annotations.VisibleForTesting;
|
||
|
|
||
|
/**
|
||
|
* A reference queue with an associated background thread that dequeues
|
||
|
* references and invokes {@link FinalizableReference#finalizeReferent()} on
|
||
|
* them.
|
||
|
*
|
||
|
* <p>
|
||
|
* Keep a strong reference to this object until all of the associated referents
|
||
|
* have been finalized. If this object is garbage collected earlier, the backing
|
||
|
* thread will not invoke {@code
|
||
|
* finalizeReferent()} on the remaining references.
|
||
|
*
|
||
|
* <p>
|
||
|
* As an example of how this is used, imagine you have a class {@code MyServer}
|
||
|
* that creates a a {@link java.net.ServerSocket ServerSocket}, and you would
|
||
|
* like to ensure that the {@code ServerSocket} is closed even if the
|
||
|
* {@code MyServer} object is garbage-collected without calling its
|
||
|
* {@code close} method. You <em>could</em> use a finalizer to accomplish this,
|
||
|
* but that has a number of well-known problems. Here is how you might use this
|
||
|
* class instead:
|
||
|
*
|
||
|
* <pre>
|
||
|
* public class MyServer implements Closeable {
|
||
|
* private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
|
||
|
* // You might also share this between several objects.
|
||
|
*
|
||
|
* private static final Set<Reference<?>> references = Sets.newConcurrentHashSet();
|
||
|
* // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
|
||
|
*
|
||
|
* private final ServerSocket serverSocket;
|
||
|
*
|
||
|
* private MyServer(...) {
|
||
|
* ...
|
||
|
* this.serverSocket = new ServerSocket(...);
|
||
|
* ...
|
||
|
* }
|
||
|
*
|
||
|
* public static MyServer create(...) {
|
||
|
* MyServer myServer = new MyServer(...);
|
||
|
* final ServerSocket serverSocket = myServer.serverSocket;
|
||
|
* Reference<?> reference = new FinalizablePhantomReference<MyServer>(myServer, frq) {
|
||
|
* @Override public void finalizeReferent() {
|
||
|
* references.remove(this):
|
||
|
* if (!serverSocket.isClosed()) {
|
||
|
* ...log a message about how nobody called close()...
|
||
|
* try {
|
||
|
* serverSocket.close();
|
||
|
* } catch (IOException e) {
|
||
|
* ...
|
||
|
* }
|
||
|
* }
|
||
|
* }
|
||
|
* };
|
||
|
* references.add(reference);
|
||
|
* return myServer;
|
||
|
* }
|
||
|
*
|
||
|
* @Override public void close() {
|
||
|
* serverSocket.close();
|
||
|
* }
|
||
|
* }
|
||
|
* </pre>
|
||
|
*
|
||
|
* @author Bob Lee
|
||
|
* @since 2.0 (imported from Google Collections Library)
|
||
|
*/
|
||
|
public class FinalizableReferenceQueue implements Closeable {
|
||
|
/*
|
||
|
* The Finalizer thread keeps a phantom reference to this object. When the
|
||
|
* client (for example, a map built by MapMaker) no longer has a strong
|
||
|
* reference to this object, the garbage collector will reclaim it and enqueue
|
||
|
* the phantom reference. The enqueued reference will trigger the Finalizer to
|
||
|
* stop.
|
||
|
*
|
||
|
* If this library is loaded in the system class loader,
|
||
|
* FinalizableReferenceQueue can load Finalizer directly with no problems.
|
||
|
*
|
||
|
* If this library is loaded in an application class loader, it's important that
|
||
|
* Finalizer not have a strong reference back to the class loader. Otherwise,
|
||
|
* you could have a graph like this:
|
||
|
*
|
||
|
* Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application
|
||
|
* class loader which loaded -> ReferenceMap.class which has a static ->
|
||
|
* FinalizableReferenceQueue instance
|
||
|
*
|
||
|
* Even if no other references to classes from the application class loader
|
||
|
* remain, the Finalizer thread keeps an indirect strong reference to the queue
|
||
|
* in ReferenceMap, which keeps the Finalizer running, and as a result, the
|
||
|
* application class loader can never be reclaimed.
|
||
|
*
|
||
|
* This means that dynamically loaded web applications and OSGi bundles can't be
|
||
|
* unloaded.
|
||
|
*
|
||
|
* If the library is loaded in an application class loader, we try to break the
|
||
|
* cycle by loading Finalizer in its own independent class loader:
|
||
|
*
|
||
|
* System class loader -> Application class loader -> ReferenceMap ->
|
||
|
* FinalizableReferenceQueue -> etc. -> Decoupled class loader -> Finalizer
|
||
|
*
|
||
|
* Now, Finalizer no longer keeps an indirect strong reference to the static
|
||
|
* FinalizableReferenceQueue field in ReferenceMap. The application class loader
|
||
|
* can be reclaimed at which point the Finalizer thread will stop and its
|
||
|
* decoupled class loader can also be reclaimed.
|
||
|
*
|
||
|
* If any of this fails along the way, we fall back to loading Finalizer
|
||
|
* directly in the application class loader.
|
||
|
*/
|
||
|
|
||
|
private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());
|
||
|
|
||
|
private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";
|
||
|
|
||
|
/** Reference to Finalizer.startFinalizer(). */
|
||
|
private static final Method startFinalizer;
|
||
|
static {
|
||
|
Class<?> finalizer = loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader());
|
||
|
startFinalizer = getStartFinalizer(finalizer);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The actual reference queue that our background thread will poll.
|
||
|
*/
|
||
|
final ReferenceQueue<Object> queue;
|
||
|
|
||
|
final PhantomReference<Object> frqRef;
|
||
|
|
||
|
/**
|
||
|
* Whether or not the background thread started successfully.
|
||
|
*/
|
||
|
final boolean threadStarted;
|
||
|
|
||
|
/**
|
||
|
* Constructs a new queue.
|
||
|
*/
|
||
|
public FinalizableReferenceQueue() {
|
||
|
// We could start the finalizer lazily, but I'd rather it blow up early.
|
||
|
queue = new ReferenceQueue<Object>();
|
||
|
frqRef = new PhantomReference<Object>(this, queue);
|
||
|
boolean threadStarted = false;
|
||
|
try {
|
||
|
startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
|
||
|
threadStarted = true;
|
||
|
} catch (IllegalAccessException impossible) {
|
||
|
throw new AssertionError(impossible); // startFinalizer() is public
|
||
|
} catch (Throwable t) {
|
||
|
logger.log(Level.INFO, "Failed to start reference finalizer thread."
|
||
|
+ " Reference cleanup will only occur when new references are created.", t);
|
||
|
}
|
||
|
|
||
|
this.threadStarted = threadStarted;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() {
|
||
|
frqRef.enqueue();
|
||
|
cleanUp();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Repeatedly dequeues references from the queue and invokes
|
||
|
* {@link FinalizableReference#finalizeReferent()} on them until the queue is
|
||
|
* empty. This method is a no-op if the background thread was created
|
||
|
* successfully.
|
||
|
*/
|
||
|
void cleanUp() {
|
||
|
if (threadStarted) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Reference<?> reference;
|
||
|
while ((reference = queue.poll()) != null) {
|
||
|
/*
|
||
|
* This is for the benefit of phantom references. Weak and soft references will
|
||
|
* have already been cleared by this point.
|
||
|
*/
|
||
|
reference.clear();
|
||
|
try {
|
||
|
((FinalizableReference) reference).finalizeReferent();
|
||
|
} catch (Throwable t) {
|
||
|
logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Iterates through the given loaders until it finds one that can load
|
||
|
* Finalizer.
|
||
|
*
|
||
|
* @return Finalizer.class
|
||
|
*/
|
||
|
private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
|
||
|
for (FinalizerLoader loader : loaders) {
|
||
|
Class<?> finalizer = loader.loadFinalizer();
|
||
|
if (finalizer != null) {
|
||
|
return finalizer;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads Finalizer.class.
|
||
|
*/
|
||
|
interface FinalizerLoader {
|
||
|
|
||
|
/**
|
||
|
* Returns Finalizer.class or null if this loader shouldn't or can't load it.
|
||
|
*
|
||
|
* @throws SecurityException if we don't have the appropriate privileges
|
||
|
*/
|
||
|
Class<?> loadFinalizer();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tries to load Finalizer from the system class loader. If Finalizer is in the
|
||
|
* system class path, we needn't create a separate loader.
|
||
|
*/
|
||
|
static class SystemLoader implements FinalizerLoader {
|
||
|
// This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to
|
||
|
// disable
|
||
|
// finding Finalizer on the system class path even if it is there.
|
||
|
@VisibleForTesting
|
||
|
static boolean disabled;
|
||
|
|
||
|
@Override
|
||
|
public Class<?> loadFinalizer() {
|
||
|
if (disabled) {
|
||
|
return null;
|
||
|
}
|
||
|
ClassLoader systemLoader;
|
||
|
try {
|
||
|
systemLoader = ClassLoader.getSystemClassLoader();
|
||
|
} catch (SecurityException e) {
|
||
|
logger.info("Not allowed to access system class loader.");
|
||
|
return null;
|
||
|
}
|
||
|
if (systemLoader != null) {
|
||
|
try {
|
||
|
return systemLoader.loadClass(FINALIZER_CLASS_NAME);
|
||
|
} catch (ClassNotFoundException e) {
|
||
|
// Ignore. Finalizer is simply in a child class loader.
|
||
|
return null;
|
||
|
}
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Try to load Finalizer in its own class loader. If Finalizer's thread had a
|
||
|
* direct reference to our class loader (which could be that of a dynamically
|
||
|
* loaded web application or OSGi bundle), it would prevent our class loader
|
||
|
* from getting garbage collected.
|
||
|
*/
|
||
|
static class DecoupledLoader implements FinalizerLoader {
|
||
|
private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader."
|
||
|
+ "Loading Finalizer in the current class loader instead. As a result, you will not be able"
|
||
|
+ "to garbage collect this class loader. To support reclaiming this class loader, either"
|
||
|
+ "resolve the underlying issue, or move Google Collections to your system class path.";
|
||
|
|
||
|
@Override
|
||
|
public Class<?> loadFinalizer() {
|
||
|
try {
|
||
|
/*
|
||
|
* We use URLClassLoader because it's the only concrete class loader
|
||
|
* implementation in the JDK. If we used our own ClassLoader subclass, Finalizer
|
||
|
* would indirectly reference this class loader:
|
||
|
*
|
||
|
* Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class
|
||
|
* loader
|
||
|
*
|
||
|
* System class loader will (and must) be the parent.
|
||
|
*/
|
||
|
ClassLoader finalizerLoader = newLoader(getBaseUrl());
|
||
|
return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
|
||
|
} catch (Exception e) {
|
||
|
logger.log(Level.WARNING, LOADING_ERROR, e);
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets URL for base of path containing Finalizer.class.
|
||
|
*/
|
||
|
URL getBaseUrl() throws IOException {
|
||
|
// Find URL pointing to Finalizer.class file.
|
||
|
String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
|
||
|
URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
|
||
|
if (finalizerUrl == null) {
|
||
|
throw new FileNotFoundException(finalizerPath);
|
||
|
}
|
||
|
|
||
|
// Find URL pointing to base of class path.
|
||
|
String urlString = finalizerUrl.toString();
|
||
|
if (!urlString.endsWith(finalizerPath)) {
|
||
|
throw new IOException("Unsupported path style: " + urlString);
|
||
|
}
|
||
|
urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
|
||
|
return new URL(finalizerUrl, urlString);
|
||
|
}
|
||
|
|
||
|
/** Creates a class loader with the given base URL as its classpath. */
|
||
|
URLClassLoader newLoader(URL base) {
|
||
|
// We use the bootstrap class loader as the parent because Finalizer by design
|
||
|
// uses
|
||
|
// only standard Java classes. That also means that
|
||
|
// FinalizableReferenceQueueTest
|
||
|
// doesn't pick up the wrong version of the Finalizer class.
|
||
|
return new URLClassLoader(new URL[] { base }, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads Finalizer directly using the current class loader. We won't be able to
|
||
|
* garbage collect this class loader, but at least the world doesn't end.
|
||
|
*/
|
||
|
static class DirectLoader implements FinalizerLoader {
|
||
|
@Override
|
||
|
public Class<?> loadFinalizer() {
|
||
|
try {
|
||
|
return Class.forName(FINALIZER_CLASS_NAME);
|
||
|
} catch (ClassNotFoundException e) {
|
||
|
throw new AssertionError(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Looks up Finalizer.startFinalizer().
|
||
|
*/
|
||
|
static Method getStartFinalizer(Class<?> finalizer) {
|
||
|
try {
|
||
|
return finalizer.getMethod("startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class);
|
||
|
} catch (NoSuchMethodException e) {
|
||
|
throw new AssertionError(e);
|
||
|
}
|
||
|
}
|
||
|
}
|