5

今天我得到PermGen OutOfMemory错误。

分析表明,Nearest GC Root for WebappClassLoaderis Logback thread:

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"外部化:CLASSPATHPreResources

<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.xmlscan="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.xmlTomcat 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() (例如,在关闭挂钩中)以正常关闭附加程序。

容器环境中的守护线程是危险的并导致内存泄漏,这对吗?

4

3 回答 3

5

我不完全明白我应该怎么做。目前我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 源,发现该类创建的线程名称类似于上面的 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真正解决了我的问题

于 2015-12-28T13:23:04.913 回答
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();
    }
于 2016-09-06T03:11:23.057 回答
1

容器环境中的守护线程是危险的并导致内存泄漏,这对吗?

关于守护线程的陈述是指独立应用程序的情况(如错误报告中),其中 JVM 应该在应用程序完成时关闭。非守护线程阻止 JVM 关闭。

在 JavaEE 上下文中,应用程序服务器的 JVM 在多个应用程序的潜在多个生命周期中保持相同,守护程序与非守护程序不会影响活动线程是 GC 根这一事实。

于 2016-01-07T08:20:17.710 回答