Whenever I stop or redeploy the webapp, I see lot of errors similar to,
msg=The web application [] created a ThreadLocal with key of type [] (value []) and
a value of type [] (value []) but failed to remove it when the web application was
stopped. Threads are going to be renewed over time to try and avoid probable memory leak
I'm not creating any ThreadLocals in my app but referencing many libraries which may be creating these ThreadLocals. We are currently using Tomcat 7. I've already gone through other similar questions [Memory leak when redeploying application in Tomcat or What are these warnings in catalina.out?] but all of them only suggest that this is Tomcat feature to warn you about ThreadLocals not being removed. I don't see any answer to remove ThreadLocals. I also see few ERRORs regarding thread not stopped as well,
msg=The web application [] appears to have started a thread named [] but has
failed to stop it. This is very likely to create a memory leak.
These are being logged in as ERRORs in our company's central logging system and thereby increasing the error count by our application. This certainly does not look good when we check the performance of our app. I tried the implementations from these two sources [Killing threads and Sample code from this thread], but doesn't seem to work. It removes thread/threadlocals not created by our app. What I need is to remove only the threads/threadlocals started by our webapp. Is there any way we can remove these in contextDestroyed() method of ServletContextListener? Following is my current ServletContextListener class,
public class CustomServletContextListener implements ServletContextListener {
private List<String> threadsAtStartup;
@Override
public void contextInitialized(ServletContextEvent sce) {
retrieveThreadsOnStartup();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Now deregister JDBC drivers in this context's ClassLoader:
// Get the webapp's ClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// Loop through all drivers
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() == cl) {
// This driver was registered by the webapp's ClassLoader, so deregister it:
try {
System.out.println("Deregistering JDBC driver {}: " + driver);
DriverManager.deregisterDriver(driver);
} catch (SQLException ex) {
System.out.println("Error deregistering JDBC driver {}: " + driver + "\nException: " + ex);
}
} else {
// driver was not registered by the webapp's ClassLoader and may be in use elsewhere
System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader: " + driver);
}
}
//Threads
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
threadGroup = Thread.currentThread().getThreadGroup();
Thread[] threads;
try {
threads = retrieveCurrentActiveThreads(threadGroup);
} catch (NoSuchFieldException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
return;
} catch (IllegalAccessException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
return;
}
int toBeKilledCount = 0;
int totalThreadCount = 0;
int killedTLCount = 0;
int totalTLCount = 0;
int killedITLCount = 0;
int totalITLCount = 0;
for (; totalThreadCount < threads.length; totalThreadCount++) {
Thread thread = threads[totalThreadCount];
if(thread != null) {
String threadName = thread.getName();
boolean shouldThisThreadBeKilled;
shouldThisThreadBeKilled = isThisThreadToBeKilled(Thread.currentThread(), thread);
if (shouldThisThreadBeKilled) {
//ThreadLocal
try {
removeThreadLocals("threadLocals", thread);
removeThreadLocals("inheritableThreadLocals", thread);
} catch (Exception e) {
System.out.println("\tError accessing threadLocals field of '" + threadName + "': " + e.getMessage());
}
//Stop thread
thread.interrupt();
thread = null;
toBeKilledCount++;
}
}
}
}
private void retrieveThreadsOnStartup() {
final Thread[] threads;
final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
try {
threads = retrieveCurrentActiveThreads(threadGroup);
} catch (NoSuchFieldException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
threadsAtStartup = new ArrayList<String>();
return;
} catch (IllegalAccessException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
threadsAtStartup = new ArrayList<String>();
return;
}
threadsAtStartup = new ArrayList<String>(threads.length);
for (int i = 0; i < threads.length; i++) {
final Thread thread;
try {
thread = threads[i];
if (null != thread) {
threadsAtStartup.add(thread.getName());
}
} catch (RuntimeException e) {
System.out.println("An error occured on initial Thread statement: " + e);
}
}
}
private Thread[] retrieveCurrentActiveThreads(ThreadGroup threadGroup) throws NoSuchFieldException, IllegalAccessException {
final Thread[] threads;
final Field privateThreadsField;
privateThreadsField = ThreadGroup.class.getDeclaredField("childrenThreads");
privateThreadsField.setAccessible(true);
threads = (Thread[]) privateThreadsField.get(threadGroup);
return threads;
}
private void removeThreadLocals(String fieldName, Thread thread) {
Field threadLocalsField = Thread.class.getDeclaredField(fieldName);
threadLocalsField.setAccessible(true);
Object threadLocalMap = threadLocalsField.get(thread);
Field tableField = threadLocalMap.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object table = tableField.get(threadLocalMap);
int count = 0;
for (int i = 0, length = Array.getLength(table); i < length; ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
totalTLCount++;
Object threadLocal = ((WeakReference)entry).get();
if (threadLocal != null) {
Array.set(table, i, null);
killedTLCount++;
}
}
}
}
private Boolean isThisThreadToBeKilled(Thread currentThread, Thread testThread) {
boolean toBeKilled;
String currentThreadName = currentThread.getName();
String testThreadName = testThread.getName();
System.out.println("currentThreadName: " + currentThreadName + ", testThreadName: " + testThreadName);
return !threadsAtStartup.contains(testThreadName) // this thread was not already running at startup
&& !testThreadName.equalsIgnoreCase(currentThreadName); // this is not the currently running thread
}
}
Update: I'm still not able to resolve this. Any help? Nobody ever ran into these?