我想, 当你搜索到这篇文章时, 你也是在为Tomcat服务器在停止时输出以下的日志而犯愁吧.
2013-6-26 20:18:20 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
2013-6-26 20:18:20 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
2013-6-26 20:18:20 org.apache.coyote.http11.Http11Protocol destroy
这会导致Tomcat 服务器停止后Java进程尚未停止, 继续占用内存, 必须通过 类似 kill -9 之类的命令去杀死java进程.
这是由于在服务器停止时有些线程尚未销毁所引起的, 如ThreadLocal; Scheduler 启动的线程; JDBC driver等.
在stackoverflow 上有几篇 专门讨论这问题的帖子并提供了一些解决办法.
http://stackoverflow.com/questions/4899205/tomcat-6-memory-leaks-log-entries
http://stackoverflow.com/questions/11872316/tomcat-guice-jdbc-memory-leak
http://stackoverflow.com/questions/5292349/is-this-very-likely-to-create-a-memory-leak-in-tomcat
在吸取他们的解决办法的基础上, 作进一步的总结, 认为在web.xml中添加一个ServletContextListener在Context destroy的时候做一些事情是不错的, 具体代码如下:
public class ContextDestroyListener implements ServletContextListener {
private static Logger logger = LoggerFactory.getLogger(ContextDestroyListener.class);
public static final List<String> MANUAL_DESTROY_THREAD_IDENTIFIERS = Arrays.asList("QuartzScheduler", "scheduler_Worker");
@Override
public void contextInitialized(ServletContextEvent sce) {
//Ignore
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
destroyJDBCDrivers();
destroySpecifyThreads();
}
private void destroySpecifyThreads() {
final Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (needManualDestroy(thread)) {
synchronized (this) {
try {
thread.stop();
logger.debug(String.format("Destroy %s successful", thread));
} catch (Exception e) {
logger.warn(String.format("Destroy %s error", thread), e);
}
}
}
}
}
private boolean needManualDestroy(Thread thread) {
final String threadName = thread.getName();
for (String manualDestroyThreadIdentifier : MANUAL_DESTROY_THREAD_IDENTIFIERS) {
if (threadName.contains(manualDestroyThreadIdentifier)) {
return true;
}
}
return false;
}
private void destroyJDBCDrivers() {
final Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver driver;
while (drivers.hasMoreElements()) {
driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
logger.debug(String.format("Deregister JDBC driver %s successful", driver));
} catch (SQLException e) {
logger.warn(String.format("Deregister JDBC driver %s error", driver), e);
}
}
}
}
实际使用中, 在MANUAL_DESTROY_THREAD_IDENTIFIERS列表中添加或删除在服务器停止时需要销毁的线程名的关键字即可.
注意: 在销毁线程时调用了过时的方法 stop(), 这方法能正常工作,但在JDK中是不提倡调用的, 有没有更好的选择呢?
希望读者能提供更好的办法(已经测试过Threadr的interrupt()方法, 不能达到停止线程的目的 )