Статьи

Память впустую потрачена приложением Spring Boot

Один из широко используемых ресурсов в мире сегодня — Память. Из-за неэффективного программирования удивительный (иногда «шокирующий») объем памяти тратится впустую. Мы видим, что этот шаблон повторяется в нескольких корпоративных приложениях. Чтобы доказать это, мы провели небольшое исследование. Мы проанализировали известное приложение клиники для весенних ботинок, чтобы увидеть, сколько памяти оно тратит. Это приложение было разработано сообществом, чтобы показать, как можно использовать платформу Spring для создания простых, но мощных приложений, ориентированных на базы данных.

Среда

  • Spring Boot 2.1.4.RELEASE
  • Java SDK 1.8
  • Tomcat 8.5.20
  • MySQL 5.7.26 с MySQL, Connector / J 8.0.15

Стресс тест

Для проведения стресс-теста мы использовали Apache JMeter , популярный инструмент нагрузочного тестирования с открытым исходным кодом. Мы выполнили нагрузочный тест в течение 30 минут со следующими настройками:

  • Количество потоков (пользователей) — 1000 (Количество пользователей подключается к цели)
  • Период разгона (в секундах) — 10. Сроки начала всех запросов. Согласно нашей конфигурации, каждые 0,01 секунды запускается 1 новый поток, т.е. 100 потоков в секунду.
  • Loop Count — навсегда. Эти 1000 потоков выполняют тестовые итерации вплотную.
  • Продолжительность (секунды) -1800. После запуска 1000 потоков работают непрерывно в течение 1800 секунд.
Рис: настройки Jmeter

В нашем нагрузочном тесте мы использовали следующие сценарии:  

  • Добавить нового владельца питомца в систему.
  • Просмотр информации, относящейся к владельцу домашнего животного.
  • Добавить нового питомца в систему.
  • Просмотр информации, касающейся домашнего животного.
  • Добавьте информацию, касающуюся посещения, в историю посещений питомца.
  • Обновите информацию, касающуюся домашнего животного.
  • Обновите информацию, относящуюся к владельцу домашнего животного.
  • Просмотр информации о владельце путем поиска его имени.
  • Просмотр информации всех владельцев.

Как измерить потери памяти?

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

Мы запустили дамп кучи из приложения Spring Boot Pet Clinic, когда тест выполнялся. (Есть 7 различных вариантов захвата дампа кучи из приложений Java / Android . Вы можете выбрать удобный для вас вариант).

Мы загрузили захваченный дамп кучи в инструмент HeapHero . Инструмент сгенерировал этот прекрасный отчет, показывающий, что 65% памяти теряется из-за неэффективного программирования. Да, это простое ванильное приложение, в котором, как предполагается, реализованы все передовые практики, которое также в очень известной среде тратит 65% памяти.

Рис. Диаграмма, сгенерированная HeapHero, показывающая, что 65% памяти тратится впустую из приложения Spring Boot для домашних клиник

Анализ потерь памяти

Из отчета вы можете заметить следующее:

  • 15,6% памяти теряется из-за повторяющихся строк
  • 14,6% памяти теряется из-за неэффективных примитивных массивов
  • 14,3% памяти теряется из-за дублирования примитивных массивов
  • 12,1% памяти теряется из-за неэффективных сборов

Дубликаты строк

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

Рис: дублирующиеся строки

Вы можете заметить, что 15,6% памяти теряется из-за повторяющихся строк. пожалуйста, обратите внимание

  • Строка ‘Goldi’ была создана 207,481 раз.
  • Строка ‘Visit’ была создана 132,308 раз. «Посещение» было описанием, которое мы упомянули в тестовом сценарии.
  • Строка «Бангалор» была создана 75,374 раза. «Banglore» — это название города, которое мы указали в тестовом сценарии.
  • «123123123» был создан 37 687 раз.
  • Строка ‘Mahesh’ была создана 37,687 раз.

По-видимому, «Гольди» — это имя питомца, которое было введено на экран с помощью тестового сценария. «Посещение» — это описание, введенное на экране с помощью тестового сценария. Точно так же являются значения. Но вопрос, почему так много тысяч раз создаются одни и те же строковые объекты.

Все мы знаем, что строки являются неизменяемыми (то есть, когда они созданы, их нельзя изменить). Учитывая это, почему создаются эти тысячи строк-дубликатов?

Инструмент HeapHero также сообщает путь к коду, где создаются эти повторяющиеся строки.

Рис: Кодовая дорожка, откуда берутся повторяющиеся строки

Вот высокоуровневые рекомендации по исправлению дублирующих строк в вашем приложении. Вы можете использовать стратегии, применимые к вашему заявлению.

Неэффективные Коллекции

Еще одной основной причиной потери памяти в приложении весенней загрузки для домашних животных является неэффективная реализация коллекций. Ниже приведена выдержка из отчета HeapHero:

Рис: потеря памяти из-за неэффективных сборов

Вы можете заметить, что 99% LinkedHashSet в памяти не содержат никаких элементов. Если нет элементов, зачем вообще создавать LinkedHashSet? При создании нового объекта LinkedHashSet пространство для 16 элементов резервируется в памяти. Все пространство, зарезервированное для этих 16 элементов, теперь потрачено впустую. Если вы выполняете ленивую инициализацию LinedHashset, то эта проблема не возникнет.

Плохая практика:

1
2
3
4
5
6
private LinkedHashSet<String, String>myHashSet = new LinkedHashSet();
 
public void addData(String key, String value) {
 
myHashSet.put(key, value);
}

Лучшая практика:

01
02
03
04
05
06
07
08
09
10
11
private LinkedHashSet<String, String>myHashSet;
 
public void addData(String key, String value) {
 
    If (myHashSet == null) {
 
myHashSet = new LinkedHashSet();
    }
 
myHashSet.put(key, value);
}

Аналогично, другое наблюдение: 68% ArrayList содержит только 1 элемент. Когда вы создаете объект ArrayList, пространство для 10 элементов резервируется в памяти. Это означает, что в 88% элементов ArrayList 9 пространство теряется. Если вы можете инициализировать ArrayList с емкостью, этой проблемы можно избежать.

Плохая практика: инициализация коллекций по умолчанию.

1
new ArrayList();

Лучшая практика: инициализация коллекций с емкостью

1
new ArrayList(1);

Память не дешевая

Можно возразить, что память такая дешевая, так зачем мне об этом беспокоиться? Честный вопрос Но память моих друзей недешева в эпоху облачных вычислений. Существует 4 основных вычислительных ресурса:

  1. ЦПУ
  2. объем памяти
  3. сеть
  4. Место хранения

Ваше приложение может работать на десятках, тысячах серверов приложений, работающих на экземплярах AWS EC2. В вышеупомянутых 4 вычислительных ресурсах, какой ресурс насыщается в экземпляре EC2? Я прошу вас остановиться на минуту здесь, прежде чем читать дальше. Подумайте, чтобы выяснить, какой ресурс насыщается первым.

Для большинства приложений это * память *. Процессор всегда на 30 — 60%. Там всегда есть изобилие хранения. Трудно насытить сеть (если ваше приложение не транслирует много видео контента). Таким образом, для большинства приложений память насыщается первой. Несмотря на то, что ЦП, СХД, сеть используются не полностью, просто из-за того, что память переполняется, в конечном итоге вы получаете все больше и больше экземпляров EC2. Это увеличит ваши вычислительные затраты в несколько раз.

С другой стороны, без исключения современные приложения тратят от 30 до 90% памяти из-за неэффективных методов программирования. Даже выше весенней загрузки домашняя клиника без особой бизнес-логики тратит 65% памяти. Реальные корпоративные приложения будут терять столько же или даже больше. Таким образом, если вы сможете написать эффективный для памяти код, это снизит ваши вычислительные затраты. Поскольку память является первым ресурсом, который будет насыщен, если вы сможете уменьшить потребление памяти, вы сможете запустить свое приложение на меньшем количестве экземпляров сервера. Вы можете уменьшить количество серверов на 30-40%. Это означает, что ваше управление может снизить стоимость центра обработки данных (или поставщика облачного хостинга) на 30–40%, а также расходы на обслуживание и поддержку. Это может составить несколько миллионов / миллиардов долларов в экономии средств.

Вывод

Помимо снижения стоимости вычислений, ваш клиентский опыт также станет намного лучше, когда написан эффективный для памяти код. Если вы сможете уменьшить количество объектов, создаваемых для обслуживания новых входящих запросов, ваше время отклика станет намного лучше. Поскольку создается меньше объектов, на создание и сбор мусора уходит меньше циклов ЦП. Сокращение времени отклика даст лучшее качество обслуживания клиентов.

Опубликовано на Java Code Geeks с разрешения Рама Лакшманана, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Память потрачена впустую Spring Boot Application

Мнения, высказанные участниками Java Code Geeks, являются их собственными.