在容器环境中优雅地停止 Logback
Posted
技术标签:
【中文标题】在容器环境中优雅地停止 Logback【英文标题】:Gracefully stop Logback in container environment 【发布时间】:2016-04-02 07:41:15 【问题描述】:今天我收到PermGen OutOfMemory
错误。
分析表明WebappClassLoader
的最近的GC Root是Logback线程:
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"
并通过 PreResources
外部化 CLASSPATH
:
<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-1"
线程?
我发现了一些相关的讨论,但不明白如何处理这些信息:
http://logback.10977.n7.nabble.com/How-to-stop-all-appenders-td3023.html Do I need to flush events when shutting down using logback? Correct way to stop custom logback async appender Stopping Logback System for Clean Shutdown更新我在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
通过在 ExecutorServiceUtil
I found changelog entry 的 Logback 源中查找:
打开的所有线程 ch.qos.logback.core.util.ExecutorServiceUtil#THREAD_FACTORY 是 now 守护程序,它修复了应用程序在关闭时挂起的问题 不调用 LoggerContext#stop() (LOGBACK-929)。注意 守护线程被 JVM 突然终止,这可能 导致不良结果,例如由 文件附加器。它仍然强烈推荐用于应用程序 调用 LoggerContext#stop() (例如,在关闭挂钩中) 优雅地关闭附加程序。
容器环境中的守护线程是不是很危险,会导致内存泄漏?
【问题讨论】:
【参考方案1】:我不完全明白我应该怎么做。目前我从项目pom.xml
和这一行中删除了jul-to-slf4j
桥:
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
来自logback.xml
。即使使用此行应用程序也没有"logback-1"
线程。
As suggest official docs我注册了:
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;
可以使用反射。
不确定我是否做得对。我会看看是否因为 Logback 而得到 PermGen OutOfMemory
错误。
更新在我从 ExecutorServiceUtil
类中发现引用依赖后,我检查了 Logback 源,发现该类创建的线程名称类似于上面的 bad:
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
的子类,所以以上解决方案确实解决了我的问题。
【讨论】:
感谢这也解决了我的 WebappClassLoader 泄漏问题!【参考方案2】:根据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();
【讨论】:
据我了解,您的解决方案仅适用于启用 Spring Boot 的应用程序...【参考方案3】:容器环境中的守护线程是不是很危险,会导致内存泄漏?
关于守护线程的陈述是指独立应用程序的情况(如bug report),其中JVM 应该在应用程序完成时关闭。非守护线程阻止JVM关闭。
在 JavaEE 上下文中,应用服务器的 JVM 在多个应用程序的潜在多个生命周期中保持相同,守护程序与非守护程序不会影响 live threads are GC roots 的事实。
【讨论】:
以上是关于在容器环境中优雅地停止 Logback的主要内容,如果未能解决你的问题,请参考以下文章
使用 docker-compose up 运行时如何优雅地停止 Dockerized Python ROS2 节点?