WebApplicationContext не отключается при перезагрузке сервлета

Когда я закрываю Tomcat, я наблюдаю правильное завершение работы и очистку Spring WebApplicationContext. Тем не менее, когда я повторно развертываю WAR на Spring (путем копирования новой 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);
  }
}

Не существует конфигурации, специфичной для управления поведением при перезагрузке контекста сервлета, и я предполагаю, что это должно было работать из коробки.

Есть ли способ закрыть WebApplicationContext, прежде чем продолжить процедуру перезагрузки контекста сервлета?

Я нахожусь на Spring 4.0.5, Tomcat 7.0.54, Hazelcast 3.2.1, Hibernate 4.3.4.Final.

Update

Я добавил слушателя приложения Spring для ContextClosedEvent и напечатал трассировку стека его вызова:

    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():

        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 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;
        }

Я вижу запись журнала с начала этого фрагмента, и я получаю свой 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() - это метод, в котором настроены имя экземпляра, имя группы и пароль, имена карт и индексы карты, поэтому я не думаю, что это представляет интерес здесь.

И это мой конфигуратор 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;
}

Ответ 1

В какой-то момент вы упоминали, что для журнала был NoClassDefFoundError. Вы получили это исправление, удалив эту зависимость, но затем проблема переместилась в другой класс - один из Spring собственных классов.

Это может означать, что любая из библиотек, которые у вас есть, делает что-то неполадки с загрузчиками классов или, может быть, Tomcat нуждается в инструкциях, чтобы не блокировать некоторые ресурсы. Подробнее см. о том, что ресурсы Tomcat заблокированы, и параметр <Context>, чтобы попытаться: в вашем Tomcat conf/context.xml поместить в элемент antiResourceLocking="true".

Ответ 2

Вы пытались увеличить unloadDelay (по умолчанию до 2000 мс) для контекстов Tomcat? См. http://tomcat.apache.org/tomcat-7.0-doc/config/context.html

UPDATE. Я вижу, что у вас также проблемы с журналом, возможно, стоит попробовать и зарегистрировать этот слушатель:

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 вручную (обязательно удалите destroyMethod из каретки bean):

@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. Он может вести себя иначе, чем перезагрузка одного и того же контекста. По крайней мере, вы должны иметь возможность развернуть старую версию лениво и посмотреть, не изменилось ли это.

Ответ 3

Аналогичная проблема возникает при оборванных потоках при перезапуске контейнера здесь.

Из всех ответов один интересный вопрос был у Говарда, который показывает, как очищаются эти потоки.

Есть несколько хороших обсуждений и рассуждений о том, как это может прекратить потоки здесь.

Теперь реализуем 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

С точки зрения разработки веб-приложений ServletContainer может уведомлять только о начале и до конца процесса приложения. Он использует ServletContextListener.

Конфигурация ServletContextListener в web.xml

<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 Меньше

Программного

@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 в AbstractApplicationContext из Spring. Я не уверен, что все будет хорошо или нет.

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

Ссылка 1 Ссылка 2