Изящно остановить 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"
нить?
Я нашел несколько связанных обсуждений, но не могу понять, что делать с этой информацией:
- http://logback.10977.n7.nabble.com/How-to-stop-all-appenders-td3023.html
- Нужно ли сбрасывать события при выключении через logback?
- Правильный способ остановить пользовательский вход в систему асинхронного приложения
- Остановка системы обратного входа для чистого отключения
ОБНОВЛЕНИЕ Я играю с дампом кучи в 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.