Изящно остановить Logback в среде контейнера

Сегодня я получаю PermGen OutOfMemory ошибка.

Анализ показал, что ближайший GC Root для WebappClassLoader Выходит из потока:

this     - value: org.apache.catalina.loader.WebappClassLoader #4
  <- contextClassLoader (thread object)     - class: java.lang.Thread, value: org.apache.catalina.loader.WebappClassLoader #4

который:

java.lang.Thread#11 - logback-1

Дамп потока из дампа кучи для этой темы:

"logback-1" daemon prio=5 tid=34 WAITING
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:458)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:359)
   Local Variable: java.util.concurrent.SynchronousQueue$TransferStack$SNode#1
   Local Variable: java.util.concurrent.SynchronousQueue$TransferStack#6
at java.util.concurrent.SynchronousQueue.take(SynchronousQueue.java:925)
   Local Variable: java.util.concurrent.SynchronousQueue#6
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
   Local Variable: java.util.concurrent.ThreadPoolExecutor#34
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
   Local Variable: java.util.concurrent.ThreadPoolExecutor$Worker#11
at java.lang.Thread.run(Thread.java:745)

Я использую Tomcat 8 с функцией горячего повторного развертывания reloadable="true" и экстернализованный CLASSPATH с помощью PreResources:

<Context docBase="/home/user/devel/app/src/main/webapp"
         reloadable="true">
    <Resources>
        <!-- To override application.properties and logback.xml -->
        <PreResources className="org.apache.catalina.webresources.DirResourceSet"
                       base="/home/user/devel/app/.config"
                       internalPath="/"
                       webAppMount="/WEB-INF/classes" />
    </Resources>
</Context>

а также logback.xml с scan="true":

<configuration debug="false" scan="true" scanPeriod="5 seconds">
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    ...

После сохранения изменений в /home/user/devel/app/.config/logback.xml Tomcat 8 получил уведомление (я не уверен, какой API использовался для отслеживания изменений на fs), и началось повторное развертывание приложения. Вот что случилось раньше PermGen OutOfMemory,

Как я могу изящно остановить Logback в среде контейнера?

Как остановиться "logback-1" нить?

Я нашел несколько связанных обсуждений, но не могу понять, что делать с этой информацией:

ОБНОВЛЕНИЕ Я играю с дампом кучи в visualvm, По уровню справки прыгать с плохого logback-1 нить:

lvl1 = flatten(filter(referees(heap.findObject(0xf4c77610)), "!/WebappClassLoader/(classof(it).name)"))

lvl2 = flatten(map(lvl1, "referees(it)"))

lvl3 = flatten(map(lvl2, "referees(it)"))

это относится к

ch.qos.logback.core.util.ExecutorServiceUtil$1

Путем поиска в источниках Logback для ExecutorServiceUtil Я нашел запись в журнале изменений:

Все потоки, открытые ch.qos.logback.core.util.ExecutorServiceUtil#THREAD_FACTORY, теперь являются демонами, что устраняет зависание приложения при завершении работы, когда LoggerContext#stop() не вызывается (LOGBACK-929). Обратите внимание, что потоки демона внезапно прерываются JVM, что может привести к нежелательным результатам, таким как поврежденные файлы, записанные FileAppender. До сих пор настоятельно рекомендуется, чтобы приложение вызывало LoggerContext#stop() (например, в ловушке завершения работы), чтобы корректно завершить работу дополнителей.

Правильно ли, что в среде контейнера потоки демонов опасны и ведут к утечкам памяти?

3 ответа

Решение

Я не до конца понимаю, что мне делать. В настоящее время я удаляю jul-to-slf4j мост из проекта pom.xml и эта строка:

<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>

от logback.xml, Даже с этим приложением линии не имеют "logback-1" нить.

Как предполагают официальные документы я регистрируюсь:

public class ShutdownCleanupListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) { }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (LoggerFactory.getILoggerFactory() instanceof LoggerContext) {
            ((LoggerContext) LoggerFactory.getILoggerFactory() ).stop();
        } 
    }
}

в web.xml:

<listener>
    <listener-class>com.app.servlet.ShutdownCleanupListener</listener-class>
</listener>

Чтобы удалить прямую зависимость от:

import ch.qos.logback.classic.LoggerContext;

отражение может быть использовано.

Не уверен, что я делаю правильно. Я посмотрю, если получишь PermGen OutOfMemory ошибка из-за Logback.

ОБНОВЛЕНИЕ После того, как я обнаружил ссылочную зависимость от ExecutorServiceUtil класс Я проверил источники Logback и обнаружил, что этот класс создает потоки с именами, подобными плохим выше:

 thread.setName("logback-" + threadNumber.getAndIncrement());

Этот класс используется только в ch.qos.logback.core.ContextBase и нить наклонилась внутрь:

public void stop() {
  // We don't check "started" here, because the executor service uses
  // lazy initialization, rather than being created in the start method
  stopExecutorService();
  started = false;
}

Обратите внимание, что LoggerContext это подкласс ContextBase так что решение выше действительно исправит мою проблему.

В соответствии с примером проекта Spring Boot logback, вы должны закрыть контекст для очистки системы журналирования: https://github.com/spring-projects/spring-boot/commit/10402a651f1ee51704b58985c7ef33619df2c110

Пример:

public static void main(String[] args) throws Exception {
        SpringApplication.run(SampleLogbackApplication.class, args).close();
    }

Правильно ли, что в среде контейнера потоки демонов опасны и ведут к утечкам памяти?

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

В контексте JavaEE, где JVM сервера приложений остается неизменной в течение потенциально нескольких жизненных циклов нескольких приложений, демон-демон не влияет на тот факт, что живые потоки являются корнями GC.

Другие вопросы по тегам