Увеличение количества загруженных классов в приложении Spring Boot, развернутом как WAR на Tomcat

У нас есть приложение Spring Boot, работающее на Tomcat, это веб-служба RESTful. Один и тот же файл WAR развернут на 3 экземплярах Tomcat в нашей тестовой среде, а также в производственной среде. Во время теста производительности мы заметили своеобразную проблему с некоторыми серверами. Некоторые серверы перестают отвечать после обработки около 2500 запросов. Проблема возникает на 2 из 3 рабочих серверов и на 1 из 3 тестовых серверов.

На серверах, на которых возникла проблема, при мониторинге JVM мы заметили, что количество загруженных классов продолжает увеличиваться всякий раз, когда мы запускаем тест производительности. Количество загруженных классов увеличивается с 20 тысяч до примерно 2 миллионов. Когда количество классов приближается к 2 миллионам, мониторинг JVM также показывает, что GC занимает слишком много времени, более 40 секунд. Как только он достигнет этой точки, приложение перестанет отвечать. Приложения выдают исключение OutOfMemoryException «Сжатое пространство класса». Если мы продолжим отправлять больше запросов, из журналов приложений мы увидим, что служба все еще получает запросы, но прекращает обработку на полпути.

На других серверах без проблем количество загруженных классов остается на постоянном уровне 20k. И GC тоже нормальный, занимает менее 1 секунды.

Другие тесты и поведение, которые мы заметили -

  1. Проблема возникает на локальных экземплярах Tomcat, установленных на ПК с Windows. Сервера на линуксе. Проблема возникает как в OpenJDK, так и в Oracle JDK 1.8.
  2. Мы проверили, что экземпляры Tomcat равны друг другу — мы даже клонировали рабочие серверы и помещали их на неисправные серверы.
  3. Протестировано с разными политиками GC — PS, CMS и G1, и проблемы возникают на всех трех.
  4. Протестировано путем запуска приложения в качестве автономного JAR-файла Spring Boot, и проблема исчезла. Количество классов остается постоянным, и GC ведет себя нормально.
  5. Приложение в настоящее время использует библиотеки JAXB для выполнения маршалинга/демаршаллинга XML, и мы нашли места в коде, где мы можем его оптимизировать. Рефакторинг кода и переход на библиотеку Джексона — еще один вариант.

Мои вопросы -

  • Что может вызвать разницу между несколькими серверами, когда мы развертываем один и тот же файл WAR?
  • В чем может быть разница между приложением, работающим как WAR, развернутым на Tomcat, по сравнению с запуском в качестве отдельного загрузочного приложения Spring?
  • Если мы возьмем дамп кучи JVM или выполним профилирование, на что следует обратить внимание?

person Rishi P    schedule 23.09.2019    source источник
comment
Возможно, вы столкнулись с этой известной ошибкой? bugs.openjdk.java.net/browse/JDK-8146539   -  person Mark Thomas    schedule 26.09.2019


Ответы (1)


Оказывается, это произошло из-за jar jaxb 2.1 в нашем пути к классам. Спасибо Марку за указание на известную ошибку с jaxb.

В нашем приложении явно не было jaxb-impl в качестве зависимости, поэтому поначалу это было трудно увидеть. Изучив дерево зависимостей Maven, мы обнаружили, что две разные версии загружаются из других проектов и библиотек. Наше приложение имело jaxb-impl версии 2.1 и 2.2.6 в пути к классам. Мы поместили версию 2.1 в качестве исключения в pom.xml нашего приложения, и это устранило проблему.

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

person Rishi P    schedule 27.09.2019