make ClassLoaderLeakPreventorFactory configurable and mark BootstrapClassLoader as shutdown

This commit is contained in:
Sebastian Sdorra
2019-11-21 16:16:15 +01:00
parent c944f23447
commit ff7b8ca842
3 changed files with 111 additions and 15 deletions

View File

@@ -5,7 +5,29 @@ package sonia.scm.lifecycle.classloading;
* find it in a heap dump.
*/
class BootstrapClassLoader extends ClassLoader {
/**
* Marker to find a BootstrapClassLoader, which is already shutdown.
*/
private boolean shutdown = false;
BootstrapClassLoader(ClassLoader webappClassLoader) {
super(webappClassLoader);
}
/**
* Returns {@code true} if the classloader was shutdown.
*
* @return {@code true} if the classloader was shutdown
*/
boolean isShutdown() {
return shutdown;
}
/**
* Mark the class loader as shutdown.
*/
void markAsShutdown() {
shutdown = true;
}
}

View File

@@ -5,7 +5,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp;
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp;
import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp;
import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator;
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator;
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator;
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
import sonia.scm.lifecycle.LifeCycle;
import sonia.scm.plugin.ChildFirstPluginClassLoader;
@@ -16,9 +22,9 @@ import java.io.IOException;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.UnaryOperator;
import static com.google.common.base.Preconditions.checkState;
import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
/**
* Creates and shutdown SCM-Manager ClassLoaders.
@@ -27,23 +33,25 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
private final Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
private Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
private final ClassLoader webappClassLoader;
private ClassLoader bootstrapClassLoader;
private UnaryOperator<ClassLoader> classLoaderAppendListener = c -> c;
private BootstrapClassLoader bootstrapClassLoader;
private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
@Override
public <C extends ClassLoader> C apply(C classLoader) {
return classLoader;
}
};
@VisibleForTesting
public static ClassLoaderLifeCycle create() {
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory();
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
// the SunAwtAppContextInitiator causes a lot of exceptions and we use no awt
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
// the MBeanCleanUp causes a Exception and we use no mbeans
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
return new ClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader(), classLoaderLeakPreventorFactory);
ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader);
return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory);
}
ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
@@ -51,12 +59,64 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
this.webappClassLoader = initAndAppend(webappClassLoader);
}
private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) {
// Should threads tied to the web app classloader be forced to stop at application shutdown?
boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads");
// Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads");
// Should shutdown hooks registered from the application be executed at application shutdown?
boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks");
// No of milliseconds to wait for threads to finish execution, before stopping them.
int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT);
/*
* No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
* If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
*/
int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT);
LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycle.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) );
LOG.info(" stopThreads = {}", stopThreads);
LOG.info(" stopTimerThreads = {}", stopTimerThreads);
LOG.info(" executeShutdownHooks = {}", executeShutdownHooks);
LOG.info(" threadWaitMs = {} ms", threadWaitMs);
LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs);
// use webapp classloader as safe base? or system?
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader);
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class);
shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks);
shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs);
final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class);
stopThreadsCleanUp.setStopThreads(stopThreads);
stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads);
stopThreadsCleanUp.setThreadWaitMs(threadWaitMs);
// remove awt and imageio cleanup
classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class);
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class);
classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class);
classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class);
// the MBeanCleanUp causes a Exception and we use no mbeans
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
return classLoaderLeakPreventorFactory;
}
public void initialize() {
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
}
@VisibleForTesting
void setClassLoaderAppendListener(UnaryOperator<ClassLoader> classLoaderAppendListener) {
void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
this.classLoaderAppendListener = classLoaderAppendListener;
}
@@ -84,12 +144,17 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
clap.shutdown();
clap = classLoaders.poll();
}
// be sure it is realy empty
classLoaders.clear();
classLoaders = new ArrayDeque<>();
bootstrapClassLoader.markAsShutdown();
bootstrapClassLoader = null;
}
private ClassLoader initAndAppend(ClassLoader originalClassLoader) {
private <T extends ClassLoader> T initAndAppend(T originalClassLoader) {
LOG.debug("init classloader {}", originalClassLoader);
ClassLoader classLoader = classLoaderAppendListener.apply(originalClassLoader);
T classLoader = classLoaderAppendListener.apply(originalClassLoader);
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
preventor.runPreClassLoaderInitiators();
@@ -98,6 +163,10 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
return classLoader;
}
interface ClassLoaderAppendListener {
<C extends ClassLoader> C apply(C classLoader);
}
private class ClassLoaderAndPreventor {
private final ClassLoader classLoader;

View File

@@ -70,7 +70,12 @@ class ClassLoaderLifeCycleTest {
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
lifeCycle.setClassLoaderAppendListener(c -> spy(c));
lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() {
@Override
public <C extends ClassLoader> C apply(C classLoader) {
return spy(classLoader);
}
});
lifeCycle.initialize();
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");