Рекомендации по оптимизации потребления памяти в Java | Статья в журнале «Молодой ученый»

Отправьте статью сегодня! Журнал выйдет 4 мая, печатный экземпляр отправим 8 мая.

Опубликовать статью в журнале

Автор:

Рубрика: Информационные технологии

Опубликовано в Молодой учёный №24 (314) июнь 2020 г.

Дата публикации: 09.06.2020

Статья просмотрена: 1932 раза

Библиографическое описание:

Наливайко, А. С. Рекомендации по оптимизации потребления памяти в Java / А. С. Наливайко. — Текст : непосредственный // Молодой ученый. — 2020. — № 24 (314). — С. 59-63. — URL: https://moluch.ru/archive/314/71486/ (дата обращения: 25.04.2024).



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

Ключевые слова: java, JVM, память, сборщик мусора, CPU, выделение памяти, утечка памяти.

Миграция на последнюю версию языка

Oracle постоянно улучшает производительность языка программирования с каждой новой версией. Поэтому, как один из вариантов, можно попробовать изменить версию компиляции того или иного проекта. Эта рекомендация актуально только в том случае, если над проектом введется разработка; когда миграция не такая болезненная как для бизнеса, так и для программистов, разрабатывающих ПО.

Использование сторонних аллокаторов памяти

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

Часто используемыми библиотеками являются Jemalloc и TCMalloc. Jemalloc является оптимизированным вариантом реализации функций malloc, который призван решать проблемы с фрагментацией при выделения памяти в несколько потоков возникающие на однопроцессорных и многопроцессорных системах и оптимальной утилизации ресурсов CPU.

TCMalloc (Thread-Caching Malloc) является аналогом Jemalloc от компании Google. Работает он по такому же принципу, однако его кэш избавлен от блокировок и работает в привязке к ядрам CPU, но откатывается на модель кэширования в привязке к потокам в случае отсутствия необходимой функциональности в ядре ОС (привязка кэша к CPU работает только в свежих ядрах Linux) [2].

Использование GraalVM

GraalVM — это виртуальная машина Java и JDK, основанная на HotSpot/OpenJDK и написанная на Java. GraalVM поддерживает разные языки программирования и модели выполнения, такие как JIT-компиляция и AOT-компиляция [8].

«Graal» в GraalVM — это название компилятора. Первый, и самый простой способ использования Graal — это использовать его как Java JIT компилятор. Основными возможностями являются быстрое выполнение и уменьшения времени старта и потребления памяти. Graal написан на Java, а не на C++, как большинство остальных JIT компиляторов для Java.

Начиная с JDK 10 его можно включить с помощью параметра:

$ -XX:+UseJVMCICompiler

Twitter — компания, на сегодняшний день, которая использует Graal на «боевых» серверах [4], и они говорят, что для них это оправдано, в терминах экономии реальных денег. Twitter использует Graal для исполнения приложений, написанных на Scala — Graal работает на уровне JVM байткода, т. е. применим для любого JVM языка.

Это первый вариант использования GraalVM — просто замена JIT компилятора на лучшую версию для ваших существующих Java приложений. Сильные стороны платформы Java особенно явно проявляются при работе с долго выполняющимися процессами и пиковыми нагрузками. Короткоживущие процессы, напротив, страдают от долгого времени запуска и относительно большого использования памяти.

Команда native-image по-настоящему компилирует ваш Java код и Java библиотеки, которые вы используете, в полноценный машинный код. Для компонентов среды выполнения, таких как сборщик мусора, мы запускаем нашу собственную новую VM, которая называется SubstrateVM, которая, как и Graal, также написана на Java.

Если запустить native-image файл, то мы увидим, что он запускается примерно на порядок быстрее и использует примерно на порядок меньше памяти, чем та же программа, запущенная под JVM. Запуск настолько быстр, что этого почти не заметно. Если использовать командную строку — не почувствуется та пауза, которая обычно присутствует, когда происходит запуск небольшой, короткоживущей программы под JVM

Alibaba одними из первых используют native-image. Как отмечают их разработчики, время на запуск сервиса удалось сократить в 20 раз (с 60 до 3 секунд), потребления памяти в 6 раз (с 128 до 21 мегабайта), а задержки при выполнении GC теперь не превышают более 100 миллисекунд. Ранее потребление и сборка всех микросервисов занимало порядка 100 гигабайт памяти и 4000 тысячи секунд на сборку проекта. В совокупности, им удалось получить четырехкратное улучшение показателей (до 20 гигабайт памяти и 1000 секунд на сборку проекта) [6].

При использовании native-image есть некоторые ограничения [10]. Обязательное условие: во время компиляции должны присутствовать все классы; также есть ограничения в области использования Reflection API. Зато присутствуют некоторые дополнительные преимущества перед базовой компиляцией: например, выполнение статических инициализаторов во время компиляции. Таким образом, уменьшается количество работы, выполняемой каждый раз, когда приложение загружается.

Это второе применение GraalVM — распространение и выполнение существующих Java программ, с быстрым стартом и меньшим расходом памяти. Этот способ устраняет такие проблемы с конфигурацией, как поиск нужного jar во время выполнения, а также позволяет создавать Docker образы меньшего размера. На самом деле, преимуществ у данной виртуальной машины гораздо больше, однако они затронуты в данной статье не будут, так как почти не влияют на оптимизацию производительности.

Одним из фреймворков, использующий преимущества GraalVM, является Quarkus. Разработчик фреймворка обещает очень высокую скорость запуска приложения и небольшой расход памяти [1]. Данные с сайта разработчика можно увидеть в таблицах 1 и 2.

Таблица 1

Время от старта приложения до первого ответа (в секундах)

Конфигурация

REST

REST + JPA

Quarkus + GraalVM

0.016

0.042

Quarkus + OpenJDK

0.943

2.033

Traditional Cloud Native Stack

4.3

9.5

Таблица 2

Потребление памяти (в мегабайтах)

Конфигурация

REST

REST+JPA

Quarkus + GraalVM

12

28

Quarkus + OpenJDK

73

145

Traditional Cloud Native Stack

136

209

Использование более подходящего сборщика мусора

Популярными сборщиками мусора являются ZGC, G1, Epsilon, Shenandoah.

ZGC — «ленивый» сборщик мусора; он не работает, но выделяет новую память, не перезапуская её. Однако так было до JDK 13. По предварительным анонсам, он должен был решить проблему подвисаний java приложений — заявленные паузы не должны превышать 100 мс даже на многогигабайтных кучах.

Включить можно с помощью:

$ -XX:+UnlockExperimentalVMOptions -XX:+UseZGC

G1 (Garbage First) — стандартный сборщик мусора, начиная с JDK 9. Он нацелен на системы с большим количеством памяти. С самого начала использовался однопоточный полный цикл GC. В следующей версии добавили многопоточность. Создателем выступает OpenJDK. Каждое обновление версии языка приносит улучшения; например, в JDK 12 научили возвращать неиспользуемую память из кучи в ОС. Так же, как и остальные сборщики мусора, G1 разбивает кучу на молодое и старое поколения. Сборка мусора происходит по большей части в молодом поколении, чтобы увеличить пропускную способность сборки, сборка в старом поколении происходит гораздо реже.

По умолчанию он уже включен, но если требуется, активировать его можно с помощью:

$ -XX:+UseG1GC

Epsilon — сборщик мусора, который обрабатывает выделение памяти, но не реализует какой-либо реальный механизм её восстановления памяти. Как только доступная куча Java будет исчерпана, JVM закроется. Обычно его используют для тестирования производительности, давления памяти, для чрезвычайно недолгой работы программы или где сборка мусора не предусмотрена (например, создание прошивки для умной техники; сам факт сборки мусора является ошибкой). В остальных случаях его возможности уступают другим сборщикам мусора.

Включить можно с помощью:

$ -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

Shenandoah — алгоритм сборки мусора, цель которого — гарантировать низкое время отклика (нижний предел — 10–500 миллисекунд). Это уменьшает время паузы сборщика мусора при выполнении работы по очищению одновременно с работающими потоками. У данного алгоритмы время паузы не зависит от размера кучи: будь heap размером 200 или 2 гигабайта, время паузы будет одинаковым. Является экспериментальной функцией, которая не включена в стандартную сборку OpenJDK.

Включить можно с помощью:

$ -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

Тонкая настройка JVM

Настроить JVM для эффективного использования доступной оперативной памяти непросто. Если запустить JVM с параметром –Xmx16M и ожидать, что будет использоваться не более 16 МБ памяти, то это не так.

Интересной областью памяти JVM является кэш кода JIT. По умолчанию HotSpot JVM будет использовать до 240 МБ. Если кэш кода слишком мал, в JIT может не хватить места для хранения своих данных, и в результате будет снижена производительность. Если кэш слишком велик, то память может быть потрачена впустую. При определении размера кэша важно учитывать его влияние как на использование памяти, так и на производительность.

В 32-х разрядных системах размер указателя на ячейку памяти занимает 32 бита. Следовательно, максимально доступная память, которую могут использовать 32-х битные указатели — 232 = 4294967296 байт или 4 ГБ. В 64-х разрядных системах соответственно можно ссылаться на 264 объектов. Такое огромное количество указателей излишне. Поэтому появилась опция сжатия ссылок:

$ -XX:+UseCompressedOops

Эта опция позволила уменьшить размер указателя в 64-х разрядных JVM до 32 бит.

Оптимизация программного кода

Все вышеперечисленные советы будут полезны только в том случае, если программный код принудительно не вызывает утечки памяти. Первый сценарий, который может вызвать утечку памяти — это интенсивное использование статических переменных. В Java время жизни статических полей обычно совпадает со временем работы приложения. Например, метод, наполняющий статическую коллекцию (ArrayList) объектами, при завершении не очистит сборщиком мусора память (рисунок 1).

Рис. 1. Потребление памяти при наполнении статической коллекции

Однако, если мы отбросим слово static у переменной, то это приведет к резкому изменению использования памяти (рисунок 2).

Рис. 2. Потребление памяти при наполнении обычной коллекции

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

Вторым из сценариев можно рассмотреть ошибку некорректного написания переопределяемых методов equals() и hashCode(). Коллекции HashSet и HashMap используют эти методы во многих операциях и если они не переопределены правильно, то эти методы могут стать источником потенциальных проблем, связанных с утечкой памяти.

Рассмотрим пример наполнения коллекции HashMap простым классом Person (конструктор, переменные name и age) в качестве ключа. Как известно, Map не позволяет использовать дубликаты ключей, многочисленные объекты Person, которые мы добавили, не должны увеличить занимаемую ими пространство в памяти. Поскольку изначально не был переопределен правильный метод equals(), дублирующие объекты накопились и заняли память. В этом случае потребление памяти кучи выглядит следующим образом (рисунок 3).

Рис. 3. Потребление памяти объектов без переопределенных методов equals() и hashCode()

Если правильно переопределить методы equals() и hashCode(), тогда в Map будет существовать только один объект Person, и лишняя память не будет использоваться (рисунок 4).

Рис. 4. Потребление памяти объектов с переопределенными методами equals() и hashCode()

Другим примером является использование ORM, например Hibernate, который использует методы equals() и hashCode() для анализа объектов и сохранения их в кеше. Если эти методы не переопределены, то шансы утечки памяти довольно высоки, потому что Hibernate не сможет сравнивать объекты и заполнит свой кеш их дубликатами.

Третий сценарий связан со строковыми операциями. Вызов метода substring() у строки, возвращается экземпляр String с лишь изменёнными значениями переменных length и offset — длины и смещения char-последовательности. При этом, если получить строку длиной 5000 символов и получить её префикс, используя метод substring(), то 5000 символов будут продолжать храниться в памяти. Для систем, которые получают и обрабатывают множество сообщений, это может быть серьёзной проблемой.

Избежать данной проблемы можно, используя любой из предложенных вариантов:

String prefix = new String(longString.substring(0,5)); // Первыйвариант

String prefix = longString.substring(0,5).intern(); // Второйвариант

Заключение

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

Литература:

  1. Container first. — Текст: электронный // Quarkus — Susersonic Subatomic Java: [сайт]. — URL: https://quarkus.io/ (дата обращения: 12.05.2020).
  2. TCMalloc: Thread-Caching Malloc. — Текст: электронный // TCMalloc: [сайт]. — URL: http://goog-perftools.sourceforge.net/doc/tcmalloc.html (дата обращения: 16.05.2020).
  3. Сборщик мусора G1 в Java 9. — Текст: электронный // urvanov.ru: [сайт]. — URL: https://urvanov.ru/2018/03/25/ %D1 %81 %D0 %B1 %D0 %BE %D1 %80 %D1 %89 %D0 %B8 %D0 %BA- %D0 %BC %D1 %83 %D1 %81 %D0 %BE %D1 %80 %D0 %B0-g1- %D0 %B2-java-9/ (дата обращения: 17.05.2020).
  4. GraalVM: Clearing up confusion around the term and why Twitter uses it in production. — Текст: электронный // JAXenter: [сайт]. — URL: https://jaxenter.com/graalvm-chris-thalinger-interview-163074.html (дата обращения: 17.05.2020).
  5. Top 10 Things To Do With GraalVM. — Текст: электронный // Medium: [сайт]. — URL: https://medium.com/graalvm/graalvm-ten-things-12d9111f307d (дата обращения: 24.05.2020).
  6. Static Compilation of Java Applications at Alibaba at Scale. — Текст: электронный // Medium: [сайт]. — URL: https://medium.com/graalvm/static-compilation-of-java-applications-at-alibaba-at-scale-2944163c92e.
  7. Из 8 в 13: полный обзор версий Java. Часть 2. — Текст: электронный // JavaRush: [сайт]. — URL: https://javarush.ru/groups/posts/2549-iz-8-v-13-polnihy-obzor-versiy-java-chastjh-2 (дата обращения: 26.05.2020).
  8. GraalVM. — Текст: электронный // Википедия: [сайт]. — URL: https://ru.wikipedia.org/wiki/GraalVM (дата обращения: 27.05.2020).
  9. Understanding Memory Leaks in Java. — Текст: электронный // Baeldung: [сайт]. — URL: https://www.baeldung.com/java-memory-leaks (дата обращения: 27.05.2020).
  10. GraalVM Native Image Compatibility and Optimization Guide. — Текст: электронный // GitHub: [сайт]. — URL: https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md (дата обращения: 29.05.2020).
Основные термины (генерируются автоматически): JVM, JDK, JIT, CPU, потребление памяти, утечка памяти, REST, память, сборщик мусора, ZGC.


Ключевые слова

память, Java, JVM, сборщик мусора, CPU, выделение памяти, утечка памяти

Похожие статьи

Диагностика утечек памяти в Java-приложениях | Статья...

Данная статья описывает структуру хранения используемой памяти, простые способы диагностики её утечек и временные исправления их. Ключевые слова: JVM, JDK, JIT, stack, стек, heap, threads, потоки, Class Loader, загрузчик классов, Stack Overflow, frame, фрейм, компилятор.

Исследование процессов внутри виртуальной машины Java

Ключевые слова: java, JVM, класс, виртуальная машина, загрузчик классов, память, операционная система, сборка мусора, спецификация. Java — не просто язык программирования, а целая программная платформа с широкими возможностями.

Оптимизация кэширования информации: задачи и аналитическое...

Для кэш-памяти первого уровня использовались простые чипы статической памяти. Встроенный кэш L2 дал существенный прирост

Их сменяют принципиально новые системы с иерархической архитектурой, которые включают в себя на два порядка больше CPU и HDD или...

Модуль автоматической сортировки отходов для городской...

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

Проблемы вычислений с высокой точностью при использовании...

В данной статье проанализированы характерные проблемы вычислений с высокой точностью при использовании гибридных вычислительных систем на базе графических процессоров NVIDIA. Выявлены характерные особенности графических процессоров, способные влиять на точность...

Анализ уязвимости переполнения буфера | Статья в журнале...

Одним из способов проникновения в компьютерные системы и нарушения их безопасности является использование различных уязвимостей программного обеспечения. Такие уязвимости могут принимать разнообразные формы: несовершенство протоколов, недостаточная...

Оптимизация потерь электроэнергии микропроцессоров

1) CPU Internal Thermal Control — Использование системы защиты от перегрева процессоров Intel Pentium 4/Core2.

High-k стал более толстым, но это позволило сократить ток утечки более чем в десять раз

Представив в виде графика рост производительности микросхем памяти, он...

Оптимизация алгоритма выравнивания биологических...

Однако вопросы оптимизации обращения к глобальной памяти в работах [3,4] не рассматривается.

В дальнейшем в предлагаемой статье будет описан способ оптимизации обращения к глобальной памяти на GPU с использованием программной модели CUDA при...

Внедрение системы мониторинга метрик в серверное приложение...

Memory Usage, rss — часть занимаемой процессом памяти, которая хранится в основной памяти (ОЗУ)

В случае если данные метрик памяти приходят в Байтах, а мы хотим отображать их на графиках в Мегабайтах, можно выполнять обработку входящих данных согласно правилам.

Похожие статьи

Диагностика утечек памяти в Java-приложениях | Статья...

Данная статья описывает структуру хранения используемой памяти, простые способы диагностики её утечек и временные исправления их. Ключевые слова: JVM, JDK, JIT, stack, стек, heap, threads, потоки, Class Loader, загрузчик классов, Stack Overflow, frame, фрейм, компилятор.

Исследование процессов внутри виртуальной машины Java

Ключевые слова: java, JVM, класс, виртуальная машина, загрузчик классов, память, операционная система, сборка мусора, спецификация. Java — не просто язык программирования, а целая программная платформа с широкими возможностями.

Оптимизация кэширования информации: задачи и аналитическое...

Для кэш-памяти первого уровня использовались простые чипы статической памяти. Встроенный кэш L2 дал существенный прирост

Их сменяют принципиально новые системы с иерархической архитектурой, которые включают в себя на два порядка больше CPU и HDD или...

Модуль автоматической сортировки отходов для городской...

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

Проблемы вычислений с высокой точностью при использовании...

В данной статье проанализированы характерные проблемы вычислений с высокой точностью при использовании гибридных вычислительных систем на базе графических процессоров NVIDIA. Выявлены характерные особенности графических процессоров, способные влиять на точность...

Анализ уязвимости переполнения буфера | Статья в журнале...

Одним из способов проникновения в компьютерные системы и нарушения их безопасности является использование различных уязвимостей программного обеспечения. Такие уязвимости могут принимать разнообразные формы: несовершенство протоколов, недостаточная...

Оптимизация потерь электроэнергии микропроцессоров

1) CPU Internal Thermal Control — Использование системы защиты от перегрева процессоров Intel Pentium 4/Core2.

High-k стал более толстым, но это позволило сократить ток утечки более чем в десять раз

Представив в виде графика рост производительности микросхем памяти, он...

Оптимизация алгоритма выравнивания биологических...

Однако вопросы оптимизации обращения к глобальной памяти в работах [3,4] не рассматривается.

В дальнейшем в предлагаемой статье будет описан способ оптимизации обращения к глобальной памяти на GPU с использованием программной модели CUDA при...

Внедрение системы мониторинга метрик в серверное приложение...

Memory Usage, rss — часть занимаемой процессом памяти, которая хранится в основной памяти (ОЗУ)

В случае если данные метрик памяти приходят в Байтах, а мы хотим отображать их на графиках в Мегабайтах, можно выполнять обработку входящих данных согласно правилам.

Задать вопрос