«Java — язык программирования, в котором нам не нужно заботиться об освобождении памяти, выделенной для объектов приложения» — так нам обычно говорят в самом начале изучения. Однако, после первого OutOfMemoryError в голове разработчика появляются первые сомнения в абсолютной истинности этого утверждения. Оказывается, что не вся память освобождается Garbage Collector-ом. Оказывается, могут быть утечки памяти, которые способны уронить любое, даже самое, казалось бы, продуманное и прекрасно спроектированное приложение. Разобраться в их причинах бывает довольно трудно, еще труднее их обнаружить и заранее их предупредить. Если мы хотим разрабатывать надежные решения, которые не будут приносить убытки своим простоем, мы должны уметь обнаружить проблемы с памятью. В этом нам поможет удобный и мощный инструментарий, о котором и пойдет речь в данной статье.
Представим ситуацию. Перед вами стоит задача вытащить из базы данных абсолютно все города, что у вас есть. При разработке и тестировании используете локальную базу из сотни записей и все работает отлично.
Что будет, если задача уйдет в продакшн? В действительности в базе данных могут быть десятки или сотни тысяч записей. Памяти JVM может не хватить и приложение будет падать.
Возникнет проблема — java.lang.OutOfMemoryError. Это случается, когда виртуальная машина Java (JVM) не может выделить объекту достаточно памяти из кучи.
Куча — это область памяти, которую выделяет JVM для хранения объектов. Сами объекты могут совместно использоваться разными потоками, пока работает приложение. Значит переполнение кучи — ситуация, при которой JVM уже не может выделить место в памяти для создания объектов.
Если вы заметили снижение производительности приложения, увеличение потребления памяти, частую сборку мусора или ошибки OutOfMemoryError, то все это может указывать на утечку памяти.
ЧТО ТАКОЕ УТЕЧКА ПАМЯТИ В JAVA
Утечка памяти — это ситуация, когда в куче есть объекты, которые больше не используются, но сборщик мусора не может удалить их. То есть объекты продолжают удерживаться в памяти, поскольку на них есть ссылки в другом месте. В результате сборщик мусора не может очистить это пространство. Будут блокироваться ресурсы памяти, что со временем приводит к ухудшению производительности системы. Если не выявить и не решить проблему, то все завершится ошибкой OutOfMemoryError.
Но как же возникает переполнение кучи если в Java работает сборщик мусора? Утечка памяти – это как раз ситуация, при которой объекты, более не нужные приложению, продолжают удерживаться в памяти приложения из-за наличия ссылок на них в других местах, что и сдерживает сборщик мусора от их очистки. Далее будут блокироваться ресурсы памяти, что со временем приводит к ухудшению производительности системы. Если не выявить и не решить проблему, то все завершится ошибкой OutOfMemoryError.
Частыми причинами утечки памяти являются:
- Статические ссылки. Так как статические поля связаны с классом, а не с экземпляром, то они остаются в памяти на протяжении всего срока работы приложения. Таким образом, неаккуратно созданная коллекция, в которую периодически добавляются элементы, в конечном итоге может привести к утечкам памяти.
- Кэшированные объекты. Неправильное и невнимательное кэширование объектов без их своевременного удаления может привести к переполнению кучи и, собственно, к утечке памяти.
- Внутренние классы. Так как нестатические (внутренние) классы содержат ссылку на свой внешний класс, то, если экземпляры этих внутренних классов сохраняются в приложении, они могут также сохранить и экземпляры своих внешних классов, что излишне захламляет память.
- Незакрытые ресурсы. Любое подключение к ресурсам, будь то база данных или потоки файлов, хранится в памяти, то, если забыть закрыть такие ресурсы, они так и буду впустую занимать память приложения.
- Неправильное использование коллекций. При необдуманном использовании коллекций, например, создание коллекций и добавление в них элементов без возможности их удаления. Эти объекты могут долго и бесполезно занимать место в памяти.
Как бороться с утечками памяти? Существуют два варианта, которые подробнее рассмотрим далее — профайлинг и мониторинг.
ПРОФАЙЛИНГ И МОНИТОРИНГ ПАМЯТИ
-
Мониторинг предполагает, что мы будем присматривать за работающим приложением на основе выборки данных, то есть данные в режиме реального времени не интерпретируются. На основе выборки данных можно организовать тревожные оповещения, то есть определять пороги производительности, которые переходить нельзя.
- Профилирование — это подход, который позволяет следить за метриками в JVM, выявлять проблемы, связанные с производительностью и стабильностью ПО и локализовать эти проблемы. При профилировании данные поступают в режиме реального времени и помогают подсветить проблемы с недостаточным выделением и/или использованием памяти, к примеру, можно детально рассмотреть, насколько оптимально работает сборщик мусора JVM.
Мониторинг больше подходит для задачи определения порогов производительности приложения по различным метрикам. При этом профилирование помогает «докопаться» до первопричины проблемы. Так как «копание» и исправление проблем зачастую приоритетнее, чем простое определение порогов, далее сосредоточимся на рассмотрении инструментов для профилирования.
ИНСТРУМЕНТЫ ДЛЯ ОБНАРУЖЕНИЯ УТЕЧЕК ПАМЯТИ В JAVA
Рассмотрим основные инструменты, которые нам помогут в профилировании Java-приложения:
- Java Flight Recorder (JFR) позволяет отслеживать работу JVM, включая использование CPU, памяти, сборки мусора, сетевую активность и другие параметры;
- Mission Control позволяет анализировать записанные данные JFR;
- Visual VM более простая программа для анализа данных JFR, но и менее информативная.
СБОР ДАННЫХ ЧЕРЕЗ JAVA FLIGHT RECORDER (JFR)
JDK Flight Recorder — это инструмент диагностики и мониторинга производительности, который регистрирует события в Java-приложение. Собирает показатели производительности системы во время ее работы без значительной нагрузки. JFR идет в поставке JDK, но по умолчанию выключен.
Для минимального запуска JFR достаточно прописать в параметрах команду ниже и запустить Java-приложение. Она активирует JFR и определит основные параметры его работы:
С командой идут дополнительные конфигурации, которых может быть несколько. Например, filename, которая запишет данные JFR в файл при завершении записи. По умолчанию данные будут накапливаться в памяти и пропадут при остановке приложения.
Почитать о других конфигурациях:
https://habr.com/ru/companies/krista/articles/532632/
MISSION CONTROL
Mission Control (MC) – набор инструментов для чтения и анализа JFR файлов, включающий инструменты для детального рассмотрения и составления графиков из событий JFR. Основные особенности:
- Интеграция с Java Flight Recorder:
- MC использует JFR для сбора данных о работе приложения, включая использование памяти, сборку мусора, выделение объектов и т.д.;
- JFR позволяет собирать данные с минимальными накладными расходами, что делает его подходящим для использования в production-средах;
- Анализ памяти:
- Показывает распределение памяти между объектами (heap и non-heap);
- Визуализирует использование памяти в реальном времени;
- Позволяет анализировать утечки памяти, показывая, какие объекты занимают больше всего места, и как они связаны;
- Гибкость настройки. Позволяет настраивать параметры сбора данных. Например, частота сбора, типы событий;
- Интерфейс:
- Современный и интуитивно понятный интерфейс с графиками, таблицами и диаграммами;
- Поддержка production-сред: JMC и JFR разработаны для использования в production с минимальным влиянием на производительность.
Преимущества:
- Низкие накладные расходы благодаря JFR;
- Подробная информация о выделении памяти и утечках;
- Интеграция с JDK. Не требует дополнительной установки, если используется современная версия JDK.
Недостатки:
- Требует наличия JFR, который может быть недоступен в некоторых средах. Например, в OpenJDK без коммерческой лицензии;
- Менее гибкий в настройке по сравнению с некоторыми сторонними инструментами.
ПРОФИЛИРОВАНИЕ ЧЕРЕЗ MISSION CONTROL
Mission Control позволяет использовать JFR через свой GUI и далее анализировать их. Flight Recorder на скриншоте:
- Настойка JFR в Mission Control
JFR представляет механизм использования профилей. Стандартный профиль подойдет под большинство задач и оптимизирован по производительности. Параметры профиля позволяют настроить детализацию снимаемых метрик. Изменение параметров — компромисс между производительностью и детализацией. Профили можно сохранять в файлах и использовать их для запуска из командной строки.
На графике показано, как работает Garbage Collector. Красным цветом помечено, сколько он работает. Из графика видно, что это довольно долго и ему трудно справиться с очисткой памяти. Значит в приложении что-то не так.
Как видно на графике, heap переполнен. Он достиг лимита 128 мб.
Большая часть памяти занята в byte и это нормально. Так как большинство объектов Java сводится к примитивам. Поэтому на него внимание не обращаем. Ниже мы видим Java Util Hash Map.
- Смотрим на Stack Trace приложения
В Stack Trace ищем код, который исполняет наша программа (Spring файлы нас сейчас не интересует, потому что мы не знаем конкретно, где он работал). Наша проблема — getCities.
VISUAL VM
Visual VM — облегчённый по сравнению с MC набор инструментов для профайлинга приложения, использующий дамп кучи для анализа.
Дамп кучи – снимок всех объектов в памяти в определённый момент времени.
Основные особенности:
- Мониторинг в реальном времени. Показывает использование heap и non-heap памяти, количество потоков, загрузку CPU и другие метрики;
- Профилирование памяти:
- Позволяет делать снимки памяти (heap dump) для анализа объектов и их связей;
- Показывает распределение памяти между классами и объектами;
- Анализ утечек памяти. Визуализирует объекты, которые занимают больше всего памяти, и помогает находить утечки;
- Плагины. Поддерживает плагины для расширения функциональности. Например, VisualGC для анализа сборки мусора;
- Интерфейс. Простой и понятный интерфейс, но менее современный по сравнению с JMC.
Преимущества:
- Бесплатный и открытый инструмент;
- Поддерживает множество плагинов для расширения функциональности;
- Прост в использовании и не требует сложной настройки.
Недостатки:
- Накладные расходы на профилирование могут быть выше, чем у JMC;
- Меньше возможностей для анализа в реальном времени по сравнению с JMC;
- Интерфейс менее современный и удобный.
ПРОФИЛИРОВАНИЕ ЧЕРЕЗ VISUAL VM
VisualVM — более простая в использовании программа, но и менее функциональна по сравнению с Mission Control. VisualVM имеет более понятный и лаконичный интерфейс и ниже порог входа. VisualVM дает возможность отслеживать SQL-запросы, количество выполнений и время выполнения. С ее помощью удобно находить слишком тяжелые запросы.
СРАВНЕНИЕ MISSION CONTROL И VISUAL VM
Mission Control:
- Интеграция — Входит в состав JDK (начиная с JDK 7u40)
- Профилирование памяти — Глубокий анализ через JFR
- Анализ утечек памяти — Подробный анализ с визуализацией связей объектов
- Интерфейс — Современный и интуитивно понятный
- Поддержка production — Оптимизирован для production-сред
- Расширяемость — Ограниченная
- Накладные расходы — Минимальные благодаря JFR
- Требования к лицензии — Требует коммерческой лицензии для JFR в некоторых версиях JDK
Visual VM:
- Интеграция — Входит в состав JDK, но может быть установлен отдельно
- Профилирование памяти — Анализ через heap dump
- Анализ утечек памяти — Возможен, но менее детализированный
- Интерфейс — Простой, но не менее современный
- Поддержка production — Меньше подходит для production
- Расширяемость — Поддержка плагинов
- Накладные расходы — Минимальные благодаря JFR
- Требования к лицензии — Бесплатный и открытый
ИТОГО
Возвращаясь к нашему примеру — получение большого объема данных посредством JPA может привести к утечкам памяти. В данном случае решением может стать добавление пагинации в метод получения городов, чтобы получать данные порционно.
Что же касается общего подхода к обнаружению и устранению утечек памяти, то в данной статье мы рассмотрели основные инструменты, которые могут нам позволить проанализировать потребление памяти объектами нашего приложения. Инструменты хорошо работают в связке. Данные, которые вы получаете через JFR, можно после проанализировать через Mission Control или Visual VM. Они предоставляют возможность профилирования и мониторинга JVM, но имеют некоторые различия в функциональности и возможностях. На основе сравнения можем сделать следующие выводы:
- Java Mission Control предоставляет удобный отчет с понятным описанием найденных проблем, он очень гибок и обладает богатой функциональностью. Лучше использовать такой инструмент, если:
- нужен глубокий анализ памяти с минимальными накладными расходами;
- есть необходимость использования в production-средах;
- в проекте используются современные версии JDK с поддержкой JFR;
- Visual VM обладает более простым интерфейсом, имеет более низкий порог входа, но предоставляет лишь базовые возможности для анализа производительности приложения по сравнению с Java Mission Control. Однако, для большинства случаев, связанных с поиском утечек памяти она вполне подойдет. Лучше всего данный инструмент подойдёт, если у вас:
- необходимость в простом и бесплатном инструменте для профилирования памяти;
- локальная разработка;
- желание использовать плагины для расширения функциональности.
И Visual VM, и Mission Control являются мощными инструментами профилирования памяти, однако ориентированы на разные сценарии использования. Mission Control лучше подходит для production-сред и глубокого анализа, тогда как VisualVM — более простой, бесплатный и универсальный инструмент для разработчиков. Выбор между ними будет зависеть от ваших задач, среды выполнения и требований к производительности.
статьи по теме
-
ЧитатьКак перейти с Java на Kotlin при создании веб-приложений? Ресурсы для начала изучения и мнения экспертов07.05.2024
-
ЧитатьMediaSoft Java Weekend — 4 доклада с презентациями для Java-разработчиков20.03.2024
-
ЧитатьЛайфхаки при использовании Java29.02.2024
-
ЧитатьБрокеры сообщений — что это, из чего состоят, плюсы и минусы: сравниваем Apache Kafka, Redis и RabbitMQ02.08.2023
-
ЧитатьElasticsearch — как работает система полнотекстового поиска: плюсы и минусы, альтернативы и лайфхаки03.05.2023
-
ЧитатьКлиент-серверное и межсервисное взаимодействие: разбираемся в REST, GraphQL, RPC и WebSocketСтатья на habr.com18.04.2023