如何识别和删除从Java中的webapp启动的Threads / ThreadLocals?

每当我停止或重新部署webapp时,我都会看到很多类似的错误,

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 

我不是在我的应用程序中创建任何ThreadLocals,而是引用可能正在创建这些ThreadLocals的许多库。 我们目前正在使用Tomcat 7.我已经遇到过其他类似的问题[在Tomcat中重新部署应用程序时内存泄漏或catalina.out中的这些警告是什么?]但是所有这些只是暗示这是Tomcatfunction来警告你ThreadLocals没有被删除。 我没有看到删除ThreadLocals的任何答案。 我也看到一些关于线程没有停止的错误,

 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. 

这些在我们公司的中央记录系统中作为ERROR登录,从而增加了我们的应用程序的错误数量。 当我们检查应用程序的性能时,这肯定不太好看。 我尝试了这两个来源的实现[杀死 此线程中的 线程和示例代码] ,但似乎不起作用。 它删除了我们的app不创建的线程/ threadlocals。 我需要的是只删除我们的webapp启动的线程/ threadlocals。 有没有什么办法可以在ServletContextListener的contextDestroyed()方法中删除这些? 以下是我当前的ServletContextListener类,

 public class CustomServletContextListener implements ServletContextListener { private List 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 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(); 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(); return; } threadsAtStartup = new ArrayList(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 } 

}

更新:我仍然无法解决这个问题。 有帮助吗? 没有人遇到过这些?

没有解决方案可以一次性解决所有线程漏洞问题。 通常,使用Threadlocal变量的第三方库具有某种清理API调用,可用于清除其本地线程变量。

您必须检查所有报告的threadlocal泄漏,并找到在相应库中处理它们的正确方法。 您可以在CustomServletContextListener执行此操作

例子:

log4j( javadoc ):

 LogManager.shutdown() 

jdbc驱动程序:( javadoc ):

 DriverManager.deregisterDriver(driver); 

注意:还要检查第三方库的新版本,以检查有关内存泄漏(和/或线程本地泄漏)的狐狸修复。

解决方案取决于创建这些Threads / ThreadLocal-s的库。 基本上,您需要从CustomServletContextListener.contextDestroyed()方法调用库的清理代码。

因此,找到什么是库以及如何正确关闭它。

您可以尝试使用此代码删除所有ThreadLocal

 private void cleanThreadLocals() { try { // Get a reference to the thread locals table of the current thread Thread thread = Thread.currentThread(); Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); threadLocalsField.setAccessible(true); Object threadLocalTable = threadLocalsField.get(thread); // Get a reference to the array holding the thread local variables inside the // ThreadLocalMap of the current thread Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); Field tableField = threadLocalMapClass.getDeclaredField("table"); tableField.setAccessible(true); Object table = tableField.get(threadLocalTable); // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object // is a reference to the actual ThreadLocal variable Field referentField = Reference.class.getDeclaredField("referent"); referentField.setAccessible(true); for (int i=0; i < Array.getLength(table); i++) { // Each entry in the table array of ThreadLocalMap is an Entry object // representing the thread local reference and its value Object entry = Array.get(table, i); if (entry != null) { // Get a reference to the thread local object and remove it from the table ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry); threadLocal.remove(); } } } catch(Exception e) { // We will tolerate an exception here and just log it throw new IllegalStateException(e); } } 

如果我们在停止容器时从所有线程的本地线程中删除对象,那么我们只是试图解决容器显示的错误消息的问题。 相反,目标应该是避免在容器未停止/重新启动时的一段时间内可能发生的内存泄漏。 因此,理想情况下,ThreadPool中的线程会被重用来处理不同的请求,然后在发送响应之后,通过将对象保留在本地线程中,不应该有任何理由来保存内存,因为该线程可能用于向客户端提供下一个(完全不同的)请求。 其中一个建议是通过配置在服务器发送响应之前立即执行的Filter来从本地线程中删除任何对象。

我会尝试找到哪个库导致这些ThreadLocal,可能是通过在调试器中运行Web应用程序并停止创建ThreadLocal。 然后你可以看看你是否忘了在一些图书馆后面清理,或者图书馆是否有错误/没有为网络应用程序使用。 也许在这里发表您的发现。

在上下文侦听器中清理线程时,我曾经检查过线程的contextClassLoader是否与运行侦听器的线程相同,以避免弄乱其他应用程序的线程。

希望这可以帮助。

尝试

 Runtime.getRuntime().addShutdownHook(webapp.new ShutdownHook()); 

在你的shudownhook中,清理对象

如果在代码中使用ThreadLocal,可以用我制作的ImrpovedThreadLocal替换ThreadLocal,并且在停止/重新部署时不会出现内存泄漏。 您可以以相同的方式使用该threadLocal,而不会发生任何线程争用。