Привет, Хабр!

Shenandoah GC — это сборщик мусора для OpenJDK, целью которого является минимизация времени пауз, возникающих в результате сборки мусора, путем выполнения процессов эвакуации памяти параллельно с работающими Java‑потоками. Фичей Shenandoah является то, что время пауз GC не зависит от размера кучи, что означает одинаково короткие времена пауз как для кучи размером в 200 МБ, так и для 200 ГБ. Это достигается за счет использования дополнительного указателя косвенности для каждого Java‑объекта, что позволяет потокам GC компактизировать кучу, пока Java‑потоки продолжают выполняться.

Разработка Shenandoah началась с экспериментального проекта, целью которого было предоставление альтернативы другим сборщикам мусора, которые приоритизируют пропускную способность или размер памяти над отзывчивостью. Уже к 12-й версии JDK Shenandoah был готов к использованию, хотя и оставался помеченным как экспериментальная функция. Это состояние сохранялось, чтобы соответствовать статусу других сборщиков мусора, таких как Epsilon GC и ZGC.

Основное событие в истории Shenandoah — включение его в состав продуктовых функций начиная с 15-й версии JDK. Т.е для его использования больше не требовалось разблокировать экспериментальные опции VM. Такое изменение было в основном косметическим и касалось классификации настроек Shenandoah. Тем не менее, это был значительный шаг вперед, подтверждающий зрелость этого сборщика мусора.

Фазы работы Shenandoah GC

Основные фазы работы Shenandoah GC включают:

Инициация маркировки: запускает конкурентную маркировку, подготавливает кучу и потоки приложения для конкурентной маркировки, а затем сканирует корневой набор. Это первая пауза в цикле, и основным потребителем времени является сканирование корневого набора.

Конкурентная маркировка: проходит по куче и отслеживает достижимые объекты. Фаза выполняется параллельно с приложением, и ее продолжительность зависит от кол-во живых объектов и структуры графа объектов в куче.

Финальная маркировка: завершает конкурентную маркировку, опорожняя все ожидающие очереди маркировки/обновления и повторно сканируя корневой набор. Также инициирует эвакуацию, определяя регионы для эвакуации.

Конкурентная очистка: восстанавливает регионы кучи, которые полностью свободны от живых объектов, обнаруженных после конкурентной маркировки.

Конкурентная эвакуация: копирует объекты из набора коллекций в другие регионы. Эта фаза также выполняется параллельно с приложением, и ее продолжительность зависит от размера выбранного набора коллекций для цикла.

Инициация обновления ссылок: инициирует фазу обновления ссылок, подготавливая GC для следующей фазы. Это третья пауза в цикле, самая короткая из всех.

Конкурентное обновление ссылок: проходит по куче и обновляет ссылки на объекты, которые были перемещены во время конкурентной эвакуации. Фаза выполняется параллельно с приложением.

Финальное обновление ссылок: завершает фазу обновления ссылок, повторно обновляя существующий корневой набор. Также перерабатывает регионы из набора коллекций.

Конкурентная очистка: восстанавливает регионы набора коллекций, которые теперь не имеют ссылок​.

Настройка Shenandoah GC в JVM

Основные параметры настройки Shenandoah GC в JVM:

-XX:+UseShenandoahGC: параметр включает использование Shenandoah GC. Пример использования: java -XX:+UseShenandoahGC MyApplication.

-XX:ShenandoahGCHeuristics: управляет эвристикой Shenandoah. Например, aggressive для уменьшения времени пауз за счет увеличения использования CPU и памяти. Пример использования: java -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive MyApplication.

-XX:ShenandoahFreeThreshold: задает процент свободной памяти в куче после сборки мусора. Пример использования: java -XX:+UseShenandoahGC -XX:ShenandoahFreeThreshold=20 MyApplication.

-XX:ShenandoahAllocationThreshold: устанавливает порог для начала сборки мусора на основе процента занятой памяти в куче. Пример использования: java -XX:+UseShenandoahGC -XX:ShenandoahAllocationThreshold=60 MyApplication.

-XX:ShenandoahGarbageThreshold: определяет процент мусора в куче, при достижении которого начнется сборка мусора. Пример использования: java -XX:+UseShenandoahGC -XX:ShenandoahGarbageThreshold=20 MyApplication.

ShenandoahUncommitDelay: параметр определяет задержку (в мс) перед тем, как Shenandoah GC освободит память, которая была выделена для кучи, но в данный момент не используется. По умолчанию значение этого параметра равно 1000 мс. Пример использования: java -XX:+UseShenandoahGC -XX:ShenandoahUncommitDelay=500 MyApplication (устанавливает задержку в 500 мс).

ShenandoahGCMode: параметр позволяет выбрать режим работы Shenandoah GC. Возможные значения включают:

normal: стандартный режим работы.

iu: (Immediate Update) режим, в котором обновление ссылок происходит сразу после копирования объекта.

satb: (Snapshot At The Beginning) режим, в котором используется алгоритм снимка состояния кучи в начале фазы маркировки.

Запуск сборщика

Добавляем параметр -XX:+UseShenandoahGC в командную строку при запуске приложения:

java -XX:+UseShenandoahGC -jar my-application.jar

Настроим сборщик так:

java -XX:+UseShenandoahGC \
     -XX:+UnlockExperimentalVMOptions \
     -XX:ShenandoahGCHeuristics=adaptive \
     -XX:+AlwaysPreTouch \
     -Xms4G -Xmx4G \
     -jar my-application.jar

-XX:+UseShenandoahGC: включает использование Shenandoah GC.

-XX:+UnlockExperimentalVMOptions: разблокирует экспериментальные опции VM

-XX:ShenandoahGCHeuristics=adaptive: устанавливает эвристику Shenandoah на «адаптивную», что позволяет сборщику мусора автоматически адаптироваться к различным условиям работы приложения.

-XX:+AlwaysPreTouch: заставляет JVM заранее выделить и инициализировать всю память кучи при запуске.

-Xms4G -Xmx4G: устанавливает начальный и максимальный размер кучи в 4 гигабайта.

Сравнение с другими сборщиками

Параметр / Сборщик

Shenandoah

G1

Parallel GC

Serial GC

CMS

Алгоритм

Разделение поколений, эвакуация областей

Разделение поколений, эвакуация областей

Остановка-мира

Остановка-мира

Параллельная отметка, параллельная очистка

Паузы

Короткие, не зависят от размера кучи

Короткие, могут увеличиваться с размером кучи

Относительно короткие, зависят от размера кучи

Долгие, зависят от размера кучи

Короткие, не зависят от размера кучи

Производительность

Высокая, особенно при больших кучах

Хорошая, но может ухудшаться при больших кучах

Высокая, но сбои при сборке мусора

Низкая, используется в основном для маленьких приложений

Хорошая, но возможны задержки из-за сборки мусора

Применение

Подходит для приложений с большим объемом памяти и требованиями к низким паузам

Подходит для приложений с большим объемом памяти и умеренными требованиями к паузам

Хорош для многопоточных приложений с большим объемом памяти

Подходит для простых или однопоточных приложений

Подходит для приложений с низкими требованиями к паузам


Сборщик мусора Shenandoah подходит для приложений, где важны короткие паузы на сборку мусора, независимо от размера кучи.

Больше практических инструментов Java-разработки эксперты из OTUS рассматривают в рамках практического онлайн-курса. Узнать о курсе подробнее можно по ссылке.