Подтвердить что ты не робот

Как идентифицировать и удалить Threads/ThreadLocals, инициированные с нашего webapp в Java?

Всякий раз, когда я останавливаю или повторно развертываю webapp, я вижу много ошибок, похожих на,

msg=The web application [] created a ThreadLocal with key of type [] (value []) and 
a value of type [] (value []) but failed to remove it when the web application was 
stopped. Threads are going to be renewed over time to try and avoid probable memory leak

Я не создаю ThreadLocals в своем приложении, но ссылаюсь на многие библиотеки, которые могут создавать эти ThreadLocals. В настоящее время мы используем Tomcat 7. Я уже рассмотрел другие подобные вопросы [Утечка памяти при повторном развертывании приложения в Tomcat или Что эти предупреждения в Catalina.out?], но все они только предполагают, что это функция Tomcat, чтобы предупредить вас о том, что ThreadLocals не удаляется. Я не вижу ответа на удаление ThreadLocals. Я также вижу несколько ОШИБКИ относительно потока, который не остановлен,

msg=The web application [] appears to have started a thread named [] but has
failed to stop it. This is very likely to create a memory leak.

Они регистрируются как ERROR в нашей центральной системе регистрации компании и тем самым увеличивают количество ошибок в нашем приложении. Это, конечно, не очень хорошо, когда мы проверяем производительность нашего приложения. Я попробовал реализации из этих двух источников [Убийство потоков и Пример кода из this thread], но, похоже, не работает. Он удаляет thread/threadlocals, не созданные нашим приложением. Мне нужно удалить только потоки /threadlocals, запущенные нашим webapp. Есть ли способ удалить их в методе contextDestroyed() ServletContextListener? Ниже приведен мой текущий класс ServletContextListener,

public class CustomServletContextListener implements ServletContextListener {

private List<String> threadsAtStartup;

@Override
public void contextInitialized(ServletContextEvent sce) {
    retrieveThreadsOnStartup();
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
    // Now deregister JDBC drivers in this context ClassLoader:
    // Get the webapp ClassLoader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Loop through all drivers
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
        Driver driver = drivers.nextElement();
        if (driver.getClass().getClassLoader() == cl) {
            // This driver was registered by the webapp ClassLoader, so deregister it:
            try {
                System.out.println("Deregistering JDBC driver {}: " + driver);
                DriverManager.deregisterDriver(driver);
            } catch (SQLException ex) {
                System.out.println("Error deregistering JDBC driver {}: " + driver + "\nException: " + ex);
            }
        } else {
            // driver was not registered by the webapp ClassLoader and may be in use elsewhere
            System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp ClassLoader: " + driver);
        }
    }

    //Threads
    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    threadGroup = Thread.currentThread().getThreadGroup();
    Thread[] threads;
    try {
        threads = retrieveCurrentActiveThreads(threadGroup);
    } catch (NoSuchFieldException e) {
        System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
        return;
    } catch (IllegalAccessException e) {
        System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
        return;
    }

    int toBeKilledCount = 0;
    int totalThreadCount = 0;
    int killedTLCount = 0;
    int totalTLCount = 0;
    int killedITLCount = 0;
    int totalITLCount = 0;
    for (; totalThreadCount < threads.length; totalThreadCount++) {
        Thread thread = threads[totalThreadCount];
        if(thread != null) {
            String threadName = thread.getName();
            boolean shouldThisThreadBeKilled;

            shouldThisThreadBeKilled = isThisThreadToBeKilled(Thread.currentThread(), thread);
            if (shouldThisThreadBeKilled) {
                //ThreadLocal
                try {
                    removeThreadLocals("threadLocals", thread);
                    removeThreadLocals("inheritableThreadLocals", thread);
                } catch (Exception e) {
                    System.out.println("\tError accessing threadLocals field of '" + threadName + "': " + e.getMessage());
                }

                //Stop thread
                thread.interrupt();
                thread = null;
                toBeKilledCount++;
            }

        }
    }
}

private void retrieveThreadsOnStartup() {
    final Thread[] threads;
    final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    try {
        threads = retrieveCurrentActiveThreads(threadGroup);
    } catch (NoSuchFieldException e) {
        System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
        threadsAtStartup = new ArrayList<String>();
        return;
    } catch (IllegalAccessException e) {
        System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
        threadsAtStartup = new ArrayList<String>();
        return;
    }

    threadsAtStartup = new ArrayList<String>(threads.length);
    for (int i = 0; i < threads.length; i++) {
        final Thread thread;
        try {
            thread = threads[i];
            if (null != thread) {
                threadsAtStartup.add(thread.getName());
            }
        } catch (RuntimeException e) {
            System.out.println("An error occured on initial Thread statement: " + e);
        }
    }
}

private Thread[] retrieveCurrentActiveThreads(ThreadGroup threadGroup) throws NoSuchFieldException, IllegalAccessException {
    final Thread[] threads;
    final Field privateThreadsField;
    privateThreadsField = ThreadGroup.class.getDeclaredField("childrenThreads");
    privateThreadsField.setAccessible(true);

    threads = (Thread[]) privateThreadsField.get(threadGroup);
    return threads;
}

private void removeThreadLocals(String fieldName, Thread thread) {
    Field threadLocalsField = Thread.class.getDeclaredField(fieldName);
    threadLocalsField.setAccessible(true);
    Object threadLocalMap = threadLocalsField.get(thread);
    Field tableField = threadLocalMap.getClass().getDeclaredField("table");
    tableField.setAccessible(true);
    Object table = tableField.get(threadLocalMap);
    int count = 0;
    for (int i = 0, length = Array.getLength(table); i < length; ++i) {
        Object entry = Array.get(table, i);
        if (entry != null) {
            totalTLCount++;
            Object threadLocal = ((WeakReference)entry).get();
            if (threadLocal != null) {
                Array.set(table, i, null);
                killedTLCount++;
            }
        } 
    }   
}

private Boolean isThisThreadToBeKilled(Thread currentThread, Thread testThread) {
    boolean toBeKilled;
    String currentThreadName = currentThread.getName();
    String testThreadName = testThread.getName();
    System.out.println("currentThreadName: " + currentThreadName + ", testThreadName: " + testThreadName);
    return !threadsAtStartup.contains(testThreadName)               // this thread was not already running at startup
            && !testThreadName.equalsIgnoreCase(currentThreadName); // this is not the currently running thread

}

}

Обновление: Я все еще не могу это решить. Любая помощь? Никто никогда не сталкивался с этим?

4b9b3361

Ответ 1

Нет никакого решения, чтобы исправить все threadlocal утечки за один раз. Обычно сторонние библиотеки, использующие переменные Threadlocal, имеют какой-то вызов API очистки, который можно использовать для очистки своих переменных локального потока.

Вы должны проверить все обнаруженные утечки потока и найти правильный способ их размещения в соответствующей библиотеке. Вы можете сделать это в своем CustomServletContextListener

примеры:

log4j (javadoc):

LogManager.shutdown()

jdbc driver: (javadoc):

DriverManager.deregisterDriver(driver);

Примечание. Также проверяйте наличие новых версий ваших сторонних библиотек для проверки исправлений лисицы в отношении утечек памяти (и/или локальных утечек потока).

Ответ 2

Решение зависит от библиотеки, создавшей эти Threads/ThreadLocal-s. В основном вам нужно вызвать код очистки библиотеки из вашего метода CustomServletContextListener.contextDestroyed().

Итак, найдите, что такое библиотека и как ее закрыть.

Ответ 3

Вы можете попробовать этот код, чтобы удалить все ThreadLocal

private void cleanThreadLocals() {
    try {
        // Get a reference to the thread locals table of the current thread
        Thread thread = Thread.currentThread();
        Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
        threadLocalsField.setAccessible(true);
        Object threadLocalTable = threadLocalsField.get(thread);

        // Get a reference to the array holding the thread local variables inside the
        // ThreadLocalMap of the current thread
        Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
        Field tableField = threadLocalMapClass.getDeclaredField("table");
        tableField.setAccessible(true);
        Object table = tableField.get(threadLocalTable);

        // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
        // is a reference to the actual ThreadLocal variable
        Field referentField = Reference.class.getDeclaredField("referent");
        referentField.setAccessible(true);

        for (int i=0; i < Array.getLength(table); i++) {
            // Each entry in the table array of ThreadLocalMap is an Entry object
            // representing the thread local reference and its value
            Object entry = Array.get(table, i);
            if (entry != null) {
                // Get a reference to the thread local object and remove it from the table
                ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
                threadLocal.remove();
            }
        }
    } catch(Exception e) {
        // We will tolerate an exception here and just log it
        throw new IllegalStateException(e);
    }
}

Ответ 4

Если мы удаляем объекты из потока локально всех потоков при остановке контейнера, мы пытаемся решить проблему сообщений об ошибках, отображаемых контейнером. Скорее, цель должна заключаться в том, чтобы избежать утечек памяти, которые могут возникнуть в течение периода времени, когда контейнер не останавливается/не перезапускается. Таким образом, в идеале, когда потоки ThreadPool повторно используются для обслуживания разных запросов, тогда после отправки ответа не должно быть никаких причин удерживать память, сохраняя объекты в потоке локальными, потому что этот поток может использоваться для обслуживания следующего (совершенно другого) запроса от клиента, Одно из предложений заключается в удалении любых объектов из потока local путем настройки фильтра, который выполняется непосредственно перед отправкой ответа от сервера.

Ответ 5

Я попытался бы найти, какая библиотека вызывает эти ThreadLocal, возможно, запустив веб-приложение в отладчике и прекратив при создании ThreadLocal. Затем вы можете увидеть, забыли ли вы очистить библиотеку или если библиотека не работает/не используется для использования в веб-приложении. Возможно, опубликуйте свои результаты здесь.

При очистке потоков в прослушивателе контекста я когда-то проверял, что contextClassLoader потока был таким же, как поток, выполняющий прослушиватель, чтобы избежать беспорядка потоков других приложений.

Надеюсь, что это поможет.

Ответ 6

попробуйте

Runtime.getRuntime().addShutdownHook(webapp.new ShutdownHook());

в вашем shudownhook, очистите объекты

Ответ 7

Если вы используете ThreadLocal в своем коде, вы можете заменить этот ThreadLocal ImrpovedThreadLocal, который я сделал, и у вас не будет утечки памяти при остановке/перераспределить. Вы можете использовать этот threadLocal таким же образом, не имея никакого конфликта нитей.