Tomcat 停止时 JAVA进程未停止 的解决方法

我想, 当你搜索到这篇文章时, 你也是在为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()方法, 不能达到停止线程的目的 )





评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值