Объекты Apache Avro ThreadLocal задерживаются при отмене развертывания Tomcat

Мы используем Apache Avro в качестве интерфейса JSON между нашим приложением Python и некоторыми сторонними библиотеками Java, которые мы запускаем в службе Tomcat. Мы решили просто расширить класс org.apache.avro.ipc.ResponderServlet для реализации нашего собственного сервлета. Сервлет действительно прост, поскольку он создает экземпляр суперкласса ResponderServlet в конструкторе и переопределяет методы init() и destroy(), чтобы выполнять некоторую работу со сторонними библиотеками, которые мы запускаем в сервлете.

Однако, когда Tomcat отменяет развертывание нашего веб-приложения, мы видим ряд СЕРЬЕЗНЫХ ошибок, предупреждающих об утечках памяти, связанных с ThreadLocal.

SEVERE: The web application [/hotwire] created a ThreadLocal with key of type [org.apache.avro.Schema$3] (value [org.apache.avro.Schema$3@4464784f]) and a value of type [java.lang.Boolean] (value [true]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
Jan 24, 2013 2:19:36 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [/hotwire] created a ThreadLocal with key of type [org.apache.avro.generic.GenericDatumReader$1] (value [org.apache.avro.generic.GenericDatumReader$1@2016ad9d]) and a value of type [org.apache.avro.util.WeakIdentityHashMap] (value [org.apache.avro.util.WeakIdentityHashMap@30e02ee0]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Вероятно, мы где-то делаем что-то наивное, потому что не смогли найти никакой помощи в Интернете для этого сценария. Тем не менее, мы надеемся, что кто-то здесь может сказать нам, где мы идем не так.

Вот беглый взгляд на наш сервлет.

public class HotWire extends ResponderServlet{
    public HotWire() throws IOException
    {
              super(new SpecificResponder(Engine.class, new EngineImpl()));
    }

    @Override
        public void init() {
        try {
         super.init();
                 try {
                      init_engine();                
                      } catch (EngineInitException e) {
                      e.printStackTrace();
                      }
            } catch (ServletException e) {
                 e.printStackTrace();
            }
        }

        @Override
        public void destroy() {
            super.destroy();
            shutdown_engine();
        }

        public static class EngineImpl implements EngineInterface  {
            public Boolean create(Parameters message) {
                Boolean status = null;
                try {
                    status = engine.create_object(message);
                } catch (SemanticException | IOException e) {
                    e.printStackTrace();
                }
                return status;
            }

}

person Satwik    schedule 25.01.2013    source источник


Ответы (1)


Я вижу аналогичную проблему. Если вы посмотрите здесь, то увидите статический кеш ThreadLocal, который заполняется без возможности его удаления:

https://github.com/apache/avro/blob/master/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java#L106

  private static final ThreadLocal<Map<Schema, Map<Schema, ResolvingDecoder>>> RESOLVER_CACHE = ThreadLocal
      .withInitial(WeakIdentityHashMap::new);

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

Это также пример утечки загрузчика классов: ThreadLocal & Memory Leak

Здесь я вижу запрос JIRA на улучшение: https://issues.apache.org/jira/browse/AVRO-1595

Обновление: мне удалось обойти проблему с утечкой памяти. Был ExecutorService, который повторно использовал потоки. Теперь я отключаю службу, которая приводит к завершению потоков. Это позволяет памяти ThreadLocal быть GC'd.

Как кто-то указал, RESOLVER_CACHE использует WeakIdentityHashMap, который должен позволять GC'ировать кеш, но у меня этого не происходило.

person Chad Juliano    schedule 23.04.2019