Skip to content

Commit

Permalink
Adding PreClassLoaderInitiators and ClassLoaderPreMortemCleanUps to C…
Browse files Browse the repository at this point in the history
…lassLoaderLeakPreventorFactory and ClassLoaderLeakPreventor. Refactoring ClassLoaderLeakPreventorListener to use registration feature. #42
  • Loading branch information
Mattias Jiderhamn committed Feb 14, 2016
1 parent c12329a commit ef047d7
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package se.jiderhamn.classloader.leak.prevention;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.security.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
Expand All @@ -15,6 +17,14 @@ public class ClassLoaderLeakPreventor {

private static final AccessControlContext NO_DOMAINS_ACCESS_CONTROL_CONTEXT = new AccessControlContext(NO_DOMAINS);

/* TODO
private final Field java_security_AccessControlContext$combiner;
private final Field java_security_AccessControlContext$parent;
private final Field java_security_AccessControlContext$privilegedContext;
*/

/**
* {@link ClassLoader} to be used when invoking the {@link PreClassLoaderInitiator}s.
* This will normally be the {@link ClassLoader#getSystemClassLoader()}, but could be any other framework or
Expand All @@ -27,17 +37,37 @@ public class ClassLoaderLeakPreventor {

private final Logger logger;

private final Collection<PreClassLoaderInitiator> preClassLoaderInitiators;

private final Collection<ClassLoaderPreMortemCleanUp> cleanUps;

/** {@link DomainCombiner} that filters any {@link ProtectionDomain}s loaded by our classloader */
private final DomainCombiner domainCombiner;

ClassLoaderLeakPreventor(ClassLoader leakSafeClassLoader, ClassLoader classLoader, Logger logger) {
ClassLoaderLeakPreventor(ClassLoader leakSafeClassLoader, ClassLoader classLoader, Logger logger,
Collection<PreClassLoaderInitiator> preClassLoaderInitiators,
Collection<ClassLoaderPreMortemCleanUp> cleanUps) {
this.leakSafeClassLoader = leakSafeClassLoader;
this.classLoader = classLoader;
this.logger = logger;
this.preClassLoaderInitiators = preClassLoaderInitiators;
this.cleanUps = cleanUps;

domainCombiner = createDomainCombiner();
}

/** Invoke all the registered {@link PreClassLoaderInitiator}s in the {@link #leakSafeClassLoader} */
public void runPreClassLoaderInitiators() {
doInLeakSafeClassLoader(new Runnable() {
@Override
public void run() {
for(PreClassLoaderInitiator preClassLoaderInitiator : preClassLoaderInitiators) {
preClassLoaderInitiator.doOutsideClassLoader(logger);
}
}
});
}

/**
* Perform action in the provided ClassLoader (normally system ClassLoader, that may retain references to the
* {@link Thread#contextClassLoader}.
Expand All @@ -47,7 +77,7 @@ public class ClassLoaderLeakPreventor {
* avoid leaking. This however means the {@link AccessControlContext} will have a {@link DomainCombiner} referencing the
* classloader, which will be taken care of in {@link #stopThreads()} TODO!!!.
*/
public void doInLeakSafeClassLoader(final Runnable runnable) { // TODO Make non-private
public void doInLeakSafeClassLoader(final Runnable runnable) { // TODO Make non-public
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

try {
Expand Down Expand Up @@ -116,6 +146,43 @@ public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionD
};
}

/**
* Recursively unset our custom {@link DomainCombiner} (loaded in the web app) from the {@link AccessControlContext}
* and any parents or privilegedContext thereof.
* TODO: Consider extracting to {@link ClassLoaderPreMortemCleanUp}
*/
/* TODO
public void removeDomainCombiner(Thread thread, AccessControlContext accessControlContext) {
if(accessControlContext != null) {
if(getFieldValue(java_security_AccessControlContext$combiner, accessControlContext) == this.domainCombiner) {
warn(AccessControlContext.class.getSimpleName() + " of thread " + thread + " used custom combiner - unsetting");
try {
java_security_AccessControlContext$combiner.set(accessControlContext, null);
}
catch (Exception e) {
error(e);
}
}
// Recurse
if(java_security_AccessControlContext$parent != null) {
removeDomainCombiner(thread, (AccessControlContext) getFieldValue(java_security_AccessControlContext$parent, accessControlContext));
}
if(java_security_AccessControlContext$privilegedContext != null) {
removeDomainCombiner(thread, (AccessControlContext) getFieldValue(java_security_AccessControlContext$privilegedContext, accessControlContext));
}
}
}
*/


/** Invoke all the registered {@link ClassLoaderPreMortemCleanUp}s */
public void runCleanUps() {
for(ClassLoaderPreMortemCleanUp cleanUp : cleanUps) {
cleanUp.cleanUp(this);
}
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utility methods

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package se.jiderhamn.classloader.leak.prevention;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import static java.util.Collections.synchronizedMap;

/**
* Orchestrator class responsible for invoking the preventative and cleanup measures.
* Contains the configuration and can be reused for multiple classloaders (assume it is not itself loaded by the
Expand All @@ -14,7 +20,30 @@ public class ClassLoaderLeakPreventorFactory {
* app server classloader.
*/
private final ClassLoader leakSafeClassLoader;

/**
* The {@link Logger} that will be passed on to the different {@link PreClassLoaderInitiator}s and
* {@link ClassLoaderPreMortemCleanUp}s
*/
private Logger logger = new LoggerImpl();

/**
* Map from name to {@link PreClassLoaderInitiator}s with all the actions to invoke in the
* {@link #leakSafeClassLoader}. Maintains insertion order. Thread safe.
*/
private final Map<String, PreClassLoaderInitiator> preInitiators =
synchronizedMap(new LinkedHashMap<String, PreClassLoaderInitiator>());

/**
* Map from name to {@link ClassLoaderPreMortemCleanUp}s with all the actions to invoke to make a
* {@link ClassLoader} ready for Garbage Collection. Maintains insertion order. Thread safe.
*/
private final Map<String, ClassLoaderPreMortemCleanUp> cleanUps =
synchronizedMap(new LinkedHashMap<String, ClassLoaderPreMortemCleanUp>());

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructors

/**
* Create new {@link ClassLoaderLeakPreventorFactory} with {@link ClassLoader#getSystemClassLoader()} as the
* {@link #leakSafeClassLoader}
Expand All @@ -31,23 +60,59 @@ public ClassLoaderLeakPreventorFactory(ClassLoader leakSafeClassLoader) {
this.leakSafeClassLoader = leakSafeClassLoader;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Factory methods

/**
* The {@link Logger} that will be passed on to the different {@link PreClassLoaderInitiator}s and
* {@link ClassLoaderPreMortemCleanUp}s
* Create new {@link ClassLoaderLeakPreventor} used to prevent the provided {@link Thread#contextClassLoader} of the
* {@link Thread#currentThread()} from leaking.
*/
private Logger logger = new LoggerImpl();
public ClassLoaderLeakPreventor newLeakPreventor() {
return newLeakPreventor(Thread.currentThread().getContextClassLoader());
}

/** Create new {@link ClassLoaderLeakPreventor} used to prevent the provided {@link ClassLoader} from leaking */
public ClassLoaderLeakPreventor newLeakPreventor(ClassLoader classLoader) {
return new ClassLoaderLeakPreventor(leakSafeClassLoader, classLoader, logger,
new ArrayList<PreClassLoaderInitiator>(preInitiators.values()), // Snapshot
new ArrayList<ClassLoaderPreMortemCleanUp>(cleanUps.values())); // Snapshot
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Methods for configuring the factory

/** Set logger */
public void setLogger(Logger logger) {
this.logger = logger;
}

// TODO factory method for Thread.currentThread().getContextClassLoader();
/** Add a new {@link PreClassLoaderInitiator}, using the class name as name */
public void addPreInitiator(PreClassLoaderInitiator preClassLoaderInitiator) {
this.preInitiators.put(preClassLoaderInitiator.getClass().getName(), preClassLoaderInitiator);
}

/** Add a new named {@link PreClassLoaderInitiator} */
public void addPreInitiator(String name, PreClassLoaderInitiator preClassLoaderInitiator) {
this.preInitiators.put(name, preClassLoaderInitiator);
}

/** Create new {@link ClassLoaderLeakPreventor} used to prevent the provided {@link ClassLoader} from leaking */
public ClassLoaderLeakPreventor newLeakPreventor(ClassLoader classLoader) {
return new ClassLoaderLeakPreventor(leakSafeClassLoader, classLoader, logger);
/** Add a new {@link ClassLoaderPreMortemCleanUp}, using the class name as name */
public void addCleanUp(ClassLoaderPreMortemCleanUp classLoaderPreMortemCleanUp) {
this.cleanUps.put(classLoaderPreMortemCleanUp.getClass().getName(), classLoaderPreMortemCleanUp);
}

/** Add a new named {@link ClassLoaderPreMortemCleanUp} */
public void addCleanUp(String name, ClassLoaderPreMortemCleanUp classLoaderPreMortemCleanUp) {
this.cleanUps.put(name, classLoaderPreMortemCleanUp);
}

/** Remove all the currently configured {@link PreClassLoaderInitiator}s */
public void clearPreInitiators() {
this.cleanUps.clear();
}

/** Remove all the currently configured {@link ClassLoaderPreMortemCleanUp}s */
public void clearCleanUps() {
this.cleanUps.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,17 @@ public class ClassLoaderLeakPreventorListener implements ServletContextListener

protected Field java_lang_ThreadLocal$ThreadLocalMap$Entry_value;

@Deprecated // TODO REMOVE
private final Field java_security_AccessControlContext$combiner;

@Deprecated // TODO REMOVE
private final Field java_security_AccessControlContext$parent;

@Deprecated // TODO REMOVE
private final Field java_security_AccessControlContext$privilegedContext;

/** {@link DomainCombiner} that filters any {@link ProtectionDomain}s loaded by our classloader */
@Deprecated // TODO REMOVE
private final DomainCombiner domainCombiner = new DomainCombiner() {
@Override
public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) {
Expand All @@ -213,6 +217,8 @@ public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionD
}
};

private ClassLoaderLeakPreventor classLoaderLeakPreventor;

/** Other {@link javax.servlet.ServletContextListener}s to use also */
protected final List<ServletContextListener> otherListeners = new LinkedList<ServletContextListener>();

Expand Down Expand Up @@ -278,15 +284,14 @@ public void contextInitialized(ServletContextEvent servletContextEvent) {

info("Initializing context by loading some known offenders with system classloader");

final ClassLoaderLeakPreventor classLoaderLeakPreventor =
new ClassLoaderLeakPreventorFactory()
.newLeakPreventor(ClassLoaderLeakPreventorListener.class.getClassLoader());

// TODO Move to constructor
final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory();

// Switch to system classloader in before we load/call some JRE stuff that will cause
// the current classloader to be available for garbage collection
classLoaderLeakPreventor.doInLeakSafeClassLoader(new Runnable() {
classLoaderLeakPreventorFactory.addPreInitiator("legacy", new PreClassLoaderInitiator() {
@Override
public void run() {
public void doOutsideClassLoader(Logger logger) {
// This part is heavily inspired by Tomcats JreMemoryLeakPreventionListener
// See http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=markup

Expand Down Expand Up @@ -323,6 +328,11 @@ public void run() {
}
});

classLoaderLeakPreventor = classLoaderLeakPreventorFactory
.newLeakPreventor(ClassLoaderLeakPreventorListener.class.getClassLoader());

classLoaderLeakPreventor.runPreClassLoaderInitiators();

for(ServletContextListener listener : otherListeners) {
try {
listener.contextInitialized(servletContextEvent);
Expand All @@ -342,8 +352,7 @@ public void run() {
* classloader, which will be taken care of in {@link #stopThreads()}.
*/
void doInSystemClassLoader(final Runnable runnable) { // TODO Remove
new ClassLoaderLeakPreventorFactory().newLeakPreventor(this.getClass().getClassLoader())
.doInLeakSafeClassLoader(runnable);
classLoaderLeakPreventor.doInLeakSafeClassLoader(runnable);
}

/**
Expand Down Expand Up @@ -1401,7 +1410,7 @@ else if(stopThreads) { // Loaded by web app
else { // Thread not running in web app - may have been started in contextInitialized() and need fixed ACC
if(inheritedAccessControlContext != null && java_security_AccessControlContext$combiner != null) {
final AccessControlContext accessControlContext = getFieldValue(inheritedAccessControlContext, thread);
removeDomainCombiner(thread, accessControlContext);
/* TODO classLoaderLeakPreventor.*/removeDomainCombiner(thread, accessControlContext);
}
}
}
Expand Down

0 comments on commit ef047d7

Please sign in to comment.