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




spring c# (7)

пытаться

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

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

Всякий раз, когда я останавливаю или переустанавливаю 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.

Они регистрируются как ОШИБКИ в центральной системе регистрации нашей компании и тем самым увеличивают количество ошибок в нашем приложении. Это, конечно, не очень хорошо, когда мы проверяем производительность нашего приложения. Я попробовал реализации из этих двух источников [Killing threads и Sample code from this thread] , но, похоже, не работает. Он удаляет thread / threadlocals, не созданные нашим приложением. Мне нужно удалить только потоки / threadlocals, запущенные нашим webapp. Есть ли способ удалить их в контексте методаDestroyed () для 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's ClassLoader:
    // Get the webapp's 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's 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's ClassLoader and may be in use elsewhere
            System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp's 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

}

}

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


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


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


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

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

Примеры:

log4j ( javadoc ):

LogManager.shutdown()

Драйвер jdbc: ( javadoc ):

DriverManager.deregisterDriver(driver);

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


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

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

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


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

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


Для синхронизации потоков я предпочитаю использовать CountDownLatch который помогает потокам ждать завершения процесса. В этом случае рабочий класс настроен с экземпляром CountDownLatch с заданным счетчиком. Метод вызова метода await будет блокироваться до тех пор, пока текущий счетчик не достигнет нуля из-за вызова метода countDown или набора тайм-аута. Этот подход позволяет мгновенно прерывать поток, не дожидаясь истечения указанного времени ожидания:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

Когда вы хотите закончить выполнение другого потока, выполните countDown в CountDownLatch и join поток к основному потоку:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null) 
        {
            countdownLatch.countDown();
        } 
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        } 
    }
}




java multithreading tomcat7 thread-local