Servlet“已启动线程但未能停止” - Tomcat中的内存泄漏

15

Apache Tomcat经常会报告:

Web应用程序[/MyServlet]似乎启动了一个名为[pool-61-thread-2]的线程,但未能停止它。这很可能会导致内存泄漏。

这危险吗?该servlet应该能够处理每天10,000个请求。如何在线程完成时关闭它们?

class Worker {

        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        private final int threadNumber;

        Worker(
                CountDownLatch startSignal,
                CountDownLatch doneSignal,
                int threadNumber
        ){

            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
            this.threadNumber = threadNumber;

        }

        public String[][] getSomeStrArrArr() {

            String[][] isRs = new String[8][20];
            String[][] inRs = new String[8][20];
            String[][] iwRs = new String[8][20];

            try {

                startSignal.await();

                if (threadNumber == 1) {
                    // get String[][] result for thread number 1
                    isRs = getIS(erg1, erg2, request);

                }

                if (threadNumber == 2) {
                    // get String[][] result for thread number 2
                    inRs = getIN(search_plz, request);
                }

                if (threadNumber == 3) {
                    // get String[][] result for thread number 3
                    iwRs = getIW(erg1, erg2, request);
                }

                doneSignal.countDown();

            } catch (InterruptedException ex) {

                System.out.println(
                        "Thread number "+threadNumber+" has been interrupted."
                );

            }
            if (threadNumber == 1) {
                return isRs;
            }
            if (threadNumber == 2) {
                return inRs;
            }
            if (threadNumber == 3) {
                return iwRs;
            }
            return null;
        }


        public Callable<String[][]> getSomeCallableStrArrArr(){
            return new Callable<String[][]>() {
                public String[][] call() throws Exception {
                    return getSomeStrArrArr();
                }
            };
        }

    }

    ExecutorService pool = Executors.newFixedThreadPool(3);
    Set<Future<String[][]>> set = new HashSet<Future<String[][]>>();
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(3);
    for (int i=1;i<=3;i++) {
        Worker worker = new Worker(startSignal,doneSignal,i);
        Callable<String[][]> callable =
                worker.getSomeCallableStrArrArr();
        Future<String[][]> future = pool.submit(callable);
        set.add(future);
    }
    startSignal.countDown();
    try {
        doneSignal.await();

1
通常在多线程环境中不应该初始化新的线程,除非你知道自己在做什么,否则让应用程序服务器处理它们。我们需要更多信息才能更好地了解您的问题,或者使用像Eclipse Memory Analyzer这样的分析工具来检查内存泄漏所在的位置。 - Luiggi Mendoza
在Tomcat中,干净的重新部署是非常脆弱的事情(不仅仅是Tomcat)。经常需要重启服务器,然后泄漏就不再是问题了。 - William F. Jameson
抱歉,请问这句话的意思是什么,如何避免这种情况? - user3876178
1个回答

18

是的,这是一个问题。如果你的代码启动了非守护线程,则这些线程会一直工作,直到它们退出其运行方法。即使其他所有内容都完成了,旧的JVM仍将在这些线程继续运行时停留。如果您启动了一个新实例,则可能会出现旧线程仍在与新实例创建的线程一起工作的情况。

任务需要设计为对中断做出响应(而不是吃掉异常并继续,这就是您的示例显示的内容)。这意味着检查当前线程上的interrupted标志,并以有用的方式捕获InterruptedException,允许任务中断其工作并在需要时重置中断标志。ExecutorService实现具有可中断当前任务的shutdownNow方法。

这是使用中断停止线程的示例

确保执行程序被关闭,您可以在ServletContextListener中处理此问题


在我的情况下,线程来自可调用对象而不是可运行对象,因为我需要从每个线程中返回。这种方式是否也适用于中断? 我需要返回结果,但不知道线程何时确切地返回... - user3876178
@user3876178:你的示例不完整,你目前有在做任何关闭池的操作吗? - Nathan Hughes
@user3876178:ExecutorService.shutdownNow。如果任务响应中断会有所帮助。 - Nathan Hughes
简单但有效!我希望它有效,但是我不再收到任何错误了。你知道这个的相关信息吗: “Web 应用程序 [/MyServlet] 似乎已经启动了一个名为 [MySQL Statement Cancellation Timer] 的线程,但未能停止它。这很可能会导致内存泄漏。”我只做了一些普通的 MySQL 调用,没有特别的东西... - user3876178
谢谢Nathan,我之前使用的是旧版的5.0.8 MySQL连接器。现在已经更新到最新版,问题/错误已经解决了! - user3876178
我因为quartz worker而出现了内存泄漏问题。如何停止jvm中的所有连接和quartz线程。我的问题在以下网址中:http://stackoverflow.com/questions/39166512/memory-leak-error-in-tomcat-server-even-after-removed-quartz-related-code - Vicky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接