Утечка памяти при перераспределении приложения в Tomcat
У меня есть WebApplication, который развернут в Tomcat 7.0.70. Я смоделировал следующую ситуацию:
- Я создал кучу кучи.
- Затем я отправил запрос Http и в методе службы я напечатал текущий поток и его классLoader. И затем я вызвал Thread.currentThread.sleep(10000).
- И в тот же момент я нажал "undeploy это приложение" на странице администрирования Tomcat.
- Я создал новую кучу кучи.
- Через несколько минут я создал новый сброс hep.
Результаты
Дамп потока
На следующем экране вы можете увидеть, что после того, как я нажал "redeploy", все потоки (которые были связаны с этим веб-приложением) были убиты, за исключением потока "http-apr-8081-exec-10" . Когда я устанавливаю атрибут Tomcat "renewThreadsWhenStoppingContext == true", вы можете увидеть, что через некоторое время этот поток ( "http-apr-8081-exec-10" ) был убит и новый поток (http-apr-8081-exec-11 ) был создан вместо него. Поэтому я не ожидал наличия старого WCL после создания кучи dump 3, потому что нет старых потоков или объектов.
![введите описание изображения здесь]()
Сброс Heapd 1
На следующих двух экранах вы можете видеть, что при запуске приложения существовал только один WCL (его параметр "начал" = true).
И в потоке "http-apr-8081-exec-10" был contextClassLoader = URLClassLoader (потому что он был в пуле Tomcat).
Я говорю только об этом потоке, потому что вы сможете увидеть, что этот поток будет обрабатывать мой будущий HTTP-запрос.
![введите описание изображения здесь]()
Отправка HTTP-запроса
Теперь я отправляю HTTP-запрос, и в своем коде я получаю информацию о текущем потоке. Вы можете видеть, что мой запрос обрабатывается потоком "http-apr-8081-exec-10"
дек 23, 2016 9:28:16 AM c.c.c.f.s.r.ReportGenerationServiceImpl INFO: request has been handled in
thread = http-apr-8081-exec-10, its contextClassLoader = WebappClassLoader
context: /hdi
delegate: false
repositories:
/WEB-INF/classes/
----------> Parent Classloader: [email protected]
Затем я нажимаю "Redeploy my web application", и я получаю следующее сообщение в консоли.
дек 23, 2016 9:28:27 AM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.
Heapd dump 2
На следующих экранах видно, что есть два экземпляра WebAppClassLoader. Один из них (номер # 1) является старым (его атрибут "начал" = ложь).
И WCL # 2 был создан после повторного развертывания приложения (его атрибут "начал" = true).
И поток, который мы рассмотрим, имеет contextClassLoader = "org.apache.catalina.loader.WebappClassLoader".
Зачем? Я ожидал увидеть contextClassLoader = "java.net.URLClassLoader" (в конце концов, когда любой поток заканчивает свою работу, он возвращается в пул Tomcat
и его атрибут "contextClassLoader" установлен в любой загрузчик базового класса).
![введите описание изображения здесь]()
![введите описание изображения здесь]()
![введите описание изображения здесь]()
Heapd dump 3
Вы можете видеть, что нет потока "http-apr-8081-exec-10" , но есть поток "http-apr-8081-exec-11", и он имеет contextClassLoader = "WebappClassLoader",
(Почему бы не URLClassLoader?).
В итоге мы имеем следующее: есть поток "http-apr-8081-exec-11", который имеет ссылку на WebappClassLoader # 1.
И obviosly, когда я делаю "Ближайший GC Root" на WCL # 1, я увижу ссылку ref в поток 11.
![введите описание изображения здесь]()
![введите описание изображения здесь]()
Вопросы.
Как я могу принудительно сказать Tomcat вернуть старое значение contextClassLoader (URLClassLoader) после того, как поток завершит свою работу?
Как я могу убедиться, что Tomcat не копирует старое значение "contextClassLoader" во время обновления потока?
Может быть, знаете ли вы другой способ решить мою проблему?
Ответы
Ответ 1
Tomcat обычно не является хорошим вариантом в производственных средах. Я использовал Tomcat для нескольких производственных приложений, и я обнаружил, что даже если размер кучи и другие конфигурации настроены правильно, и каждый раз, когда вы перезагружаете приложение, потребление памяти увеличивается и увеличивается. Пока вы не перезапустите службу tomcat, память не будет полностью восстановлена. Мы тестировали все такие эксперименты, такие как очистка журналов, передислокация всех приложений, регулярное перезапуск tomcat один раз в месяц или неделю в течение менее загруженных часов. Но в конце я должен сказать, что мы переместили нашу производственную среду на Glassfish и WebSphere.
Надеюсь, вы уже прошли эти страницы:
Утечка памяти в веб-приложении Java
Утечка памяти Tomcat Fix?
https://developers.redhat.com/blog/2014/08/14/find-fix-memory-leaks-java-application/
http://www.tomcatexpert.com/blog/2010/04/06/tomcats-new-memory-leak-prevention-and-detection
Если ваши веб-приложения не тесно связаны с Tomcat, тогда вы можете подумать об использовании другого веб-контейнера. Теперь мы используем Glassfish даже на машинах и производстве разработки, и в тот день, когда мы принимаем это решение, мы сэкономили много времени. Хотя Glassfish и другой такой сервер занимают больше времени, пока они начинаются, поскольку они не так легки, как Tomcat, но после того, как жизнь немного легче.
Ответ 2
Из моего опыта с этой проблемой, что мешало tomcat корректно загружать старшие классы класса GC, было несколько ThreadLocal
, которые использовались в нескольких фреймворках, которые я использовал, создавали (и не правильно обрабатывали).
Нечто похожее на то, что объясняется здесь: ThreadLocal и утечка памяти
Я попытался правильно завершить этот ThreadLocal
, и моя утечка уменьшилась LOT. Он все еще протекал, но я мог обрабатывать в 10 раз больше перераспределителей, чем раньше.
Я бы определенно проверил ваши дампы памяти на объекты, которые каким-то образом могут быть связаны с ThreadLocal
(они очень распространены, особенно если вы используете что-то для управления транзакциями или что-то, что изолировано от потока).
Надеюсь, это поможет!
Ответ 3
Утечка памяти при повторной установке tomcat - очень старая проблема.
Единственный реальный способ решить это - перезапустить tomcat вместо повторного развертывания приложения. Если у вас есть несколько приложений, вам нужно запустить несколько сервисов tomcat на разных портах и присоединиться к нему с помощью nginx.
Ответ 4
У нас есть сотни экземпляров Tomcat, работающих в нескольких средах (также производство), и единственным разумным решением, которое мы обнаружили в этой проблеме, является остановка и перезапуск каждого Tomcat в заданное время ежедневно (в ночное время).
Мы пробовали много трюков, но это прочное решение для наших требований времени безотказной работы.
Ответ 5
Tomcat обычно не является хорошим вариантом в производственных средах. Я использовал Tomcat для нескольких производственных приложений, и я обнаружил, что даже если размер кучи и другие конфигурации настроены правильно, и каждый раз, когда вы перезагружаете приложение, потребление памяти увеличивается и увеличивается. Пока вы не перезапустите службу tomcat, память не будет полностью восстановлена. Мы тестировали все такие эксперименты, такие как очистка журналов, передислокация всех приложений, регулярное перезапуск tomcat один раз в месяц или неделю в течение менее загруженных часов. Но в конце я должен сказать, что мы изменили нашу производственную среду на Glassfish и WebSphere.
Ответ 6
Проверьте использование ThreadLocal, которое предотвратит сбор мусора для ClassLoader. Либо удалите ссылки на свои классы в значениях ThreadLocal, либо используйте https://github.com/codesinthedark/ImprovedThreadLocal вместо ThreadLocal