Статьи

Память тратится впустую Spring Boot Application

Весенние цыплята и весенние сапоги!

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


Вам также может понравиться:
Как защитить свои коллекции Java от потери памяти

Мы проанализировали известное приложение клиники для весенних ботинок, чтобы увидеть, сколько памяти оно тратит. Это приложение было разработано сообществом, чтобы показать, как можно использовать платформу 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 (Количество пользователей подключается к цели).
  • Период разгона (в секундах) — время начала всех запросов. Согласно нашей конфигурации, каждые 0,01 секунды запускается 1 новый поток, т.е. 100 потоков в секунду.
  • Число циклов — эти 1000 потоков выполняют тестовые итерации вплотную.
  • Длительность (секунды) — после увеличения 1000 потоков работают непрерывно в течение 1800 секунд.

Потеря памяти

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

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

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

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

Мы запустили дамп кучи из приложения Spring Boot Pet Clinic, когда тест выполнялся. (Существует семь различных вариантов захвата дампа кучи из приложений 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, то эта проблема не возникнет.

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

private LinkedHashSet<String, String>myHashSet = new LinkedHashSet();

public void addData(String key, String value) {

myHashSet.put(key, value);
}

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

 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 с емкостью, этой проблемы можно избежать.

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

new ArrayList();

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

 new ArrayList(1);

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

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

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

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

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

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

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

Вывод

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

Дальнейшее чтение

Разочаровывающая история об оптимизации памяти Java

Повторяющиеся строки: как избавиться от них и сохранить память

Обнаружение утечек памяти из дампа кучи JVM