如何摆脱由休眠线程引起的 tomcat 内存泄漏日志条目

How to get rid of tomcat memory leak log entries caused by a sleeping thread

知道如何删除以下 Tomcat 日志条目:

SEVERE: The web application [/my-app] appears to have started a thread named [Thread-11] but has failed to stop it. This is very likely to create a memory leak.
Jun 29, 2015 11:14:33 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads

我正在关闭 tomcat 7.0.42.

线程转储如下所示:

"Thread-11" daemon prio=10 tid=0x00007fedc0bd5000 nid=0x2983 waiting on condition [0x00007fedbacef000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at com.my.app.Test$WorkerThread.run(Test.java:248)

java 第 248 行是:sleep(1000*60*pollIntervalMinutes);

  public void start()
{

    WorkerThread t= new WorkerThread(this);
    t.setDaemon(true);
    t.start();
}
private class WorkerThread extends Thread
    {
        Controller controller=null;
        int pollIntervalMinutes=0;
        private boolean alive = true;

        public WorkerThread(Controller controller)
        {
            this.controller=controller;
            pollIntervalMinutes=60;
        }

        @Override
        public void run()
        {
            while(alive)
            {
                try
                {
                    sleep(1000*60*pollIntervalMinutes);
                    controller.createAllProjectsIfNeeded();
                }
                catch (Exception e)
                {
                    alive = false;
                }
            }
        }
    }

谢谢

问题似乎是 Tomcat 没有尝试停止应用程序创建的非托管线程。

查找报告错误的 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 的实现,JVM 线程从清理过程中跳过。

因此,我会说永远不会抛出 InterruptedException,因此 alive 不会像预期的那样设置为 false。

 @SuppressWarnings("deprecation")
    private void clearReferencesThreads() {
        Thread[] threads = getThreads();

    // Iterate over the set of threads
    for (Thread thread : threads) {
        if (thread != null) {
            ClassLoader ccl = thread.getContextClassLoader();
            if (ccl == this) {
                // Don't warn about this thread
                if (thread == Thread.currentThread()) {
                    continue;
                }

                // JVM controlled threads
                ThreadGroup tg = thread.getThreadGroup();
                if (tg != null &&
                        JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {

                    // HttpClient keep-alive threads
                    if (clearReferencesHttpClientKeepAliveThread &&
                            thread.getName().equals("Keep-Alive-Timer")) {
                        thread.setContextClassLoader(parent);
                        log.debug(sm.getString(
                                "webappClassLoader.checkThreadsHttpClient"));
                    }

                    // Don't warn about remaining JVM controlled threads
                    continue;
                }

                // Skip threads that have already died
                if (!thread.isAlive()) {
                    continue;
                }

                // TimerThread can be stopped safely so treat separately
                if (thread.getClass().getName().equals(
                        "java.util.TimerThread") &&
                        clearReferencesStopTimerThreads) {
                    clearReferencesStopTimerThread(thread);
                    continue;
                }

                if (isRequestThread(thread)) {
                    log.error(sm.getString("webappClassLoader.warnRequestThread",
                            contextName, thread.getName()));
                } else {
                    log.error(sm.getString("webappClassLoader.warnThread",
                            contextName, thread.getName()));
                }

                // Don't try an stop the threads unless explicitly
                // configured to do so
                if (!clearReferencesStopThreads) {
                    continue;
                }

                // If the thread has been started via an executor, try
                // shutting down the executor
                try {
                    Field targetField =
                        thread.getClass().getDeclaredField("target");
                    targetField.setAccessible(true);
                    Object target = targetField.get(thread);

                    if (target != null &&
                            target.getClass().getCanonicalName().equals(
                            "java.util.concurrent.ThreadPoolExecutor.Worker")) {
                        Field executorField =
                            target.getClass().getDeclaredField("this[=10=]");
                        executorField.setAccessible(true);
                        Object executor = executorField.get(target);
                        if (executor instanceof ThreadPoolExecutor) {
                            ((ThreadPoolExecutor) executor).shutdownNow();
                        }
                    }
                } catch (SecurityException e) {
                    log.warn(sm.getString(
                            "webappClassLoader.stopThreadFail",
                            thread.getName(), contextName), e);
                } catch (NoSuchFieldException e) {
                    log.warn(sm.getString(
                            "webappClassLoader.stopThreadFail",
                            thread.getName(), contextName), e);
                } catch (IllegalArgumentException e) {
                    log.warn(sm.getString(
                            "webappClassLoader.stopThreadFail",
                            thread.getName(), contextName), e);
                } catch (IllegalAccessException e) {
                    log.warn(sm.getString(
                            "webappClassLoader.stopThreadFail",
                            thread.getName(), contextName), e);
                }

                // This method is deprecated and for good reason. This is
                // very risky code but is the only option at this point.
                // A *very* good reason for apps to do this clean-up
                // themselves.
                thread.stop();
            }
        }
    }
}

我可以想到三个选项来避免这种内存泄漏:

  • 提供关闭钩子作为了解您生成的线程并可以中断它们的线程

How to add shutdown hook

  • 不要生成会在 while 循环中无限旋转的线程,以便它们可以在完成处理后停止
  • ExecutorService 中包装线程并在应用程序关闭事件时调用 shutdownNow()

    这可以通过使用 @WebListener 注释的服务 class 来实现 并在方法中调用 shutdownNow()contextDestroyed(ServletContextEvent event).

shutdownNow()

Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.

There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate.

我建议您将 ServletContextListener 添加到您的上下文 (example) 中,它与您的线程通信并在上下文被销毁时停止它。这样当您的应用程序被卸载时(如果您删除 .war 或替换它)线程停止并启动一个新的线程。