There is a memory leak detection feature introduced in Tomcat 6.0.25 that attempts to log objects that have failed to be unregistered by webapps it hosts when they are stopped, and were forcibly unregistered by Tomcat. As Tomcat is forcibly removing these objects, it is not a serious concern that these log messages occur.web
中止tomcat服務後,報出如下錯誤:sql
嚴重: The web application [] appears to have started a thread named [Timer-0] but has failed to stop it. This is very likely to create a memory leak. 2016-9-27 21:33:48 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 嚴重: The web application [] appears to have started a thread named [pool-1-thread-1] but has failed to stop it. This is very likely to create a memory leak. 2016-9-27 21:33:48 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 嚴重: The web application [] appears to have started a thread named [DefaultQuartzScheduler_Worker-2] but has failed to stop it. This is very likely to create a memory leak. 2016-9-27 21:33:48 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
初步診斷爲服務中止時,線程池未能成功關閉,添加線程監聽器ThreadListener,在服務中止的時候中止線程池:數據庫
public class ThreadListener implements ServletContextListener { private Log logger = LogFactory.getLog(ThreadListener.class); @Override public void contextInitialized(ServletContextEvent sce) { } @Override public void contextDestroyed(ServletContextEvent sce) { try { logger.info("銷燬線程池"); ProtoExecutorService.getExecutor().shutdownNow(); } catch (Exception e) { logger.error(String.format("銷燬線程池出錯,錯誤信息:%s", e)); e.printStackTrace(); } } }
繼續運行一段時間後發現,中止tomcat服務後仍會出現一樣報錯,查找資料,http://stackoverflow.com/questions/5292349/is-this-very-likely-to-create-a-memory-leak-in-tomcat ,添加清理ThreadLocal的代碼:apache
public class ThreadLocalImmolater { private Log logger = LogFactory.getLog(ThreadLocalImmolater.class); private static ThreadLocalImmolater immolater = null; Boolean debug; public ThreadLocalImmolater() { debug = false; } public synchronized static ThreadLocalImmolater getInstance(){ if(null == immolater){ immolater = new ThreadLocalImmolater(); } return immolater; } public Integer immolate() throws Exception { int count = 0; final Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); threadLocalsField.setAccessible(true); final Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals"); inheritableThreadLocalsField.setAccessible(true); for (final Thread thread : Thread.getAllStackTraces().keySet()) { count += clear(threadLocalsField.get(thread)); count += clear(inheritableThreadLocalsField.get(thread)); if (thread != null) { thread.setContextClassLoader(null); } } logger.info("immolated " + count + " values in ThreadLocals"); return count; } private int clear(final Object threadLocalMap) throws Exception { if (threadLocalMap == null) return 0; int count = 0; final Field tableField = threadLocalMap.getClass().getDeclaredField("table"); tableField.setAccessible(true); final Object table = tableField.get(threadLocalMap); for (int i = 0, length = Array.getLength(table); i < length; ++i) { final Object entry = Array.get(table, i); if (entry != null) { final Object threadLocal = ((WeakReference)entry).get(); if (threadLocal != null) { log(i, threadLocal); Array.set(table, i, null); ++count; } } } return count; } private void log(int i, final Object threadLocal) { if (!debug) { return; } if (threadLocal.getClass() != null && threadLocal.getClass().getEnclosingClass() != null && threadLocal.getClass().getEnclosingClass().getName() != null) { logger.info("threadLocalMap(" + i + "): " + threadLocal.getClass().getEnclosingClass().getName()); } else if (threadLocal.getClass() != null && threadLocal.getClass().getName() != null) { logger.info("threadLocalMap(" + i + "): " + threadLocal.getClass().getName()); } else { logger.info("threadLocalMap(" + i + "): cannot identify threadlocal class name"); } } }
在ThreadListener中添加如下代碼:tomcat
//清理線程 try { logger.info("清理線程"); ThreadLocalImmolater.getInstance().immolate(); } catch (Exception e) { logger.error(String.format("清理線程出錯,錯誤信息:%s", e)); e.printStackTrace(); }
運行一段時間後發現,中止tomcat服務後再也不出現一樣錯誤,可是會出現app
嚴重: The web application [] registered the JDBC driver [com.microsoft.sqlserver.jdbc.SQLServerDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
在ThreadListener中添加如下代碼:webapp
logger.info("關閉數據庫鏈接"); Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); try { DriverManager.deregisterDriver(driver); logger.info(String.format("deregistering jdbc driver: %s", driver)); } catch (SQLException e) { logger.error(String.format("Error deregistering driver %s", driver), e); } }
報錯消失。ide
參考資料:sqlserver
http://fourfireliu.iteye.com/blog/2187584this
http://stackoverflow.com/questions/5292349/is-this-very-likely-to-create-a-memory-leak-in-tomcat