【问题标题】:WebApplicationContext not being shut down on Servlet Context reloadWebApplicationContext 未在 Servlet 上下文重新加载时关闭
【发布时间】:2014-09-05 17:13:30
【问题描述】:

当我关闭 Tomcat 时,我观察到 Spring WebApplicationContext 的正确关闭和清理。但是,当我重新部署基于 Spring 的 WAR(通过将新 WAR 复制到 webapps)时,不会发生正常关闭。由于所有随之而来的资源泄漏,这对我来说是个问题:

org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.scheduled] but has failed to stop it. This is very likely to create a memory leak.
org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.operation.thread-0] but has failed to stop it. This is very likely to create a memory leak.

...等等。我正在使用无 XML 配置,这是我的 WebApplicationInitializer:

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
  @Override protected Class<?>[] getRootConfigClasses() {
    return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
  }
  @Override protected Class<?>[] getServletConfigClasses() { return null; }

  @Override protected String[] getServletMappings() { return new String[] { "/" }; }

  @Override public void onStartup(ServletContext ctx) throws ServletException {
    ctx.setInitParameter("spring.profiles.active", "production");
    super.onStartup(ctx);
  }
}

没有专门用于控制 servlet 上下文重新加载时的行为的配置,我认为这应该是开箱即用的。

有没有办法让 WebApplicationContext 在继续 servlet 上下文重新加载过程之前正确关闭?

我正在使用 Spring 4.0.5、Tomcat 7.0.54、Hazelcast 3.2.1、Hibernate 4.3.4.Final。

更新

我为 ContextClosedEvent 添加了一个 Spring 应用程序侦听器并打印了其调用的堆栈跟踪:

    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:880) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:841) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.destroy(FrameworkServlet.java:819) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.apache.catalina.core.StandardWrapper.unload(StandardWrapper.java:1486) [catalina.jar:7.0.54]
    at org.apache.catalina.core.StandardWrapper.stopInternal(StandardWrapper.java:1847) [catalina.jar:7.0.54]
    at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
    at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5647) [catalina.jar:7.0.54]
    at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
    at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1575) [catalina.jar:7.0.54]
    at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1564) [catalina.jar:7.0.54]

这表明 Spring 关闭发生在其Servlet#destroy 方法中。这是来自AbstractApplicationContext#close()的相关sn-p:

        if (logger.isInfoEnabled()) {
            logger.info("Closing " + this);
        }

        LiveBeansView.unregisterApplicationContext(this);

        try {
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }
        // Stop all Lifecycle beans, to avoid delays during individual destruction.
        try {
            getLifecycleProcessor().onClose();
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
        }

        // Destroy all cached singletons in the context's BeanFactory.
        destroyBeans();

        // Close the state of this context itself.
        closeBeanFactory();

        // Let subclasses do some final clean-up if they wish...
        onClose();

        synchronized (this.activeMonitor) {
            this.active = false;
        }

我看到了从这个 sn-p 开始的日志条目,我得到了我的 ContextClosedEvent。我还看到一个条目DefaultLifecycleProcessor - Stopping beans in phase 2147483647,它可能来自getLifecycleProcessor.onClose() 行。似乎在下游发生了一些错误。一些异常可能会被吞下。

更新 2

根据要求,这是我配置 Hazelcast 的方式:

@Bean(destroyMethod="shutdown") public HazelcastInstance hazelcast() {
  final Config c = hzConfig();
  final JoinConfig join = c.getNetworkConfig().getJoin();
  join.getMulticastConfig().setEnabled(false);
  join.getTcpIpConfig().setEnabled(true);
  return getOrCreateHazelcastInstance(c);
}

hzConfig() 是配置实例名、组名和密码、地图名和地图索引的方法,所以我觉得这里没什么意思。

这是我的 Hibernate SessionFactory 配置:

@Bean
public LocalSessionFactoryBean sessionFactory() {
  final LocalSessionFactoryBean b = new LocalSessionFactoryBean();
  b.setDataSource(dataSource);
  b.setHibernateProperties(props(
      "hibernate.connection.release_mode", "on_close",
      "hibernate.id.new_generator_mappings", "true",
      "hibernate.hbm2ddl.auto", "update",
      "hibernate.order_inserts", "true",
      "hibernate.order_updates", "true",
      "hibernate.max_fetch_depth", "0",
      "hibernate.jdbc.fetch_size", "200",
      "hibernate.jdbc.batch_size", "50",
      "hibernate.jdbc.batch_versioned_data", "true",
      "hibernate.jdbc.use_streams_for_binary", "true",
      "hibernate.use_sql_comments", "true"
  ));
  return b;
}

【问题讨论】:

  • 您似乎有一些计划任务在其他线程中运行,这些任务超出了 spring 上下文,并且在 spring 上下文关闭时它们无法停止。
  • 它是 hazalcast 基础设施,当应用程序上下文关闭时,它都会正确关闭。
  • 你试过用tomcat的admin manager app重新部署你的war而不是复制它吗?
  • @JorgeCampos 我删除了管理员,因为我认为它存在安全风险。
  • 你能分享更多你的 hazelcast 配置细节吗? Tomcat 抱怨的线程与 Hazelcast 有关。

标签: java spring spring-mvc tomcat7


【解决方案1】:

在某些时候,您提到 Logback 有一个 NoClassDefFoundError。您通过删除此依赖项解决了这个问题,但随后问题转移到了另一个类 - Spring 自己的类之一。

这可能意味着您拥有的任何一个库在类加载器方面存在问题,或者 Tomcat 可能需要指示不要保持对某些资源的锁定。请参阅here 更多关于 Tomcat 资源被锁定的信息以及要尝试的 &lt;Context&gt; 设置:在 Tomcat 的 conf/context.xml 中将 antiResourceLocking="true" 放置到元素中。

【讨论】:

    【解决方案2】:

    您是否尝试过为 Tomcat 上下文增加 unloadDelay(默认为 2000 毫秒)?见http://tomcat.apache.org/tomcat-7.0-doc/config/context.html

    更新:我发现您也遇到了 logback 问题,尝试注册此侦听器可能值得一试:

    class LogbackShutdownListener implements ServletContextListener {
    
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
            System.out.println("Shutting down Logback context '" + loggerContext.getName() + "' for " + contextRootFor(event));
            loggerContext.stop();
        }
    
        @Override
        public void contextInitialized(ServletContextEvent event) {
            System.out.println("Logback context shutdown listener registered for " + contextRootFor(event));
        }
    
        private String contextRootFor(ServletContextEvent event) {
            return event.getServletContext().getContextPath();
        }
    

    }

    请务必在 spring 上下文加载器侦听器之前声明此侦听器,以便在关闭时在上下文侦听器之后调用它。

    更新 2:也可能值得尝试注册另一个 bean 来手动处理 Hazelcast 的关闭(确保也从 hazelcast bean 中删除 destroyMethod):

    @Component
    class HazelcastDestructor {
    
        @Autowired
        private HazelcastInstance instance;
    
        @PreDestroy
        public void shutdown() {
            try {
                instance.shutdown();
            } catch (Exception e) {
                System.out.println("Hazelcast failed to shutdown(): " + e);
                throw e;
            }
        }  
    }
    

    更新 3:出于好奇,您是否尝试过并行部署:http://www.javacodegeeks.com/2011/06/zero-downtime-deployment-and-rollback.html。它可能的行为与重新加载相同的上下文不同。至少你应该能够懒惰地取消部署旧版本,看看是否有什么不同。

    【讨论】:

    • 我现在可以告诉你,这是一次很棒的尝试。明天我检查后回复你。
    • 不幸的是,这并没有帮助,实际上我在上下文重新加载的第一秒内得到了 Context Destroyed 事件。
    • 无赖。请参阅编辑后的答案以尝试其他方法。
    • 我知道这些其他途径,但问题是关闭过程可能中止,所以 1) hazelcast 没有被关闭不是唯一的问题,只是“最响亮”的一个,2)这些其他途径可能也会失败,因为错误出现在高级控制代码中(请参阅我的问题中的 sn-p)。
    • 还有一件事要尝试(如果您还没有尝试过),请参阅更新 #3。
    【解决方案3】:

    在容器重新启动here 时,悬空线程也存在类似问题。

    在所有答案中,一个特别感兴趣的答案是霍华德的 - 它显示了这些线程被清除的方式。

    关于如何终止线程here,有一些很好的讨论和推理。

    现在实现 ServletContextListener 并在 contextDestroyed() 方法中处理这些线程:

        public class YourListener implements ServletContextListener{
            ....
            @Override
            public void contextDestroyed(ServletContextEvent event) {
                //Call the immolate method here
            }
        }
    

    在 WebApplicationInitilizer 中将此监听器注册为:

         ctx.addListener(new YourListener());
    

    因此,当服务器重新启动时 - 调用 contextDestroyed 方法,这会处理所有这些线程。

    【讨论】:

      【解决方案4】:

      从Web App开发的角度来看,ServletContainer只能通知app的before started和before end进程。 它正在使用ServletContextListener

      web.xml 中配置ServletContextListener

      <listener>
          <listener-class>com.var.YourListener</listener-class>
      </listener>
      

      YourListener.java

      public class YourListener implements ServletContextListener {
      
          public void contextInitialized(ServletContextEvent sce) {
              //initialization process
          }
          public void contextDestroyed(ServletContextEvent sce) {
              //destory process
          }
      }   
      

      更新 -XML Less

      以编程方式

      @Override 
      public void onStartup(ServletContext ctx) throws ServletException {
      
          ctx.addListener(new YourContextListener());
      
          ctx.setInitParameter("spring.profiles.active", "production");
          super.onStartup(ctx);
      }   
      

      注释

      @WebListener / @WebServletContextListener 
      public class YourContextListener implements ServletContextListener {
      
          public void contextInitialized(ServletContextEvent servletContextEvent) {
          }
      
          public void contextDestroyed(ServletContextEvent servletContextEvent) {
          }
      }
      

      Update-ShoutDown Hook In Spring

      在我的应用开发之前我从不使用它,我们可以将shoutdownhook event注册到Spring的AbstractApplicationContext。 我不确定它是否适合你。

      AbstractApplicationContext context = ...
      context.registerShutdownHook();
      

      Reference 1Reference 2

      【讨论】:

      • 我有一个无 XML 的 Spring 应用程序。我没有 web.xml。此外,当 Tomcat 停止时,会进行适当的清理。
      • @MarkoTopolnik,你可以像ServletContextListener一样使用org.springframework.web.context.ServletContextAware
      • 如果您查看我更新的问题,您会发现问题不在信号部分(关机程序按预期启动);问题是过程中出现故障,但在日志中没有任何痕迹。
      猜你喜欢
      • 2017-08-08
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      相关资源
      最近更新 更多