🧠 Раздел 3 · Вопрос #15

Что такое Shenandoah GC

Главная цель Shenandoah — свести паузы Stop-The-World (STW) к минимуму (от 1 до 10 мс, а в современных версиях — до долей миллисекунды) независимо от объема оперативной памяти к...


🟢 Junior Level

Shenandoah GC — это сборщик мусора с ультранизкими задержками (Low-Latency Garbage Collector), разработанный компанией Red Hat и входящий в состав OpenJDK (начиная с Java 12, с бэкпортом в Java 8 и 11).

Главная цель Shenandoah — свести паузы Stop-The-World (STW) к минимуму (от 1 до 10 мс, а в современных версиях — до долей миллисекунды) независимо от объема оперативной памяти кучи (будь то 4 ГБ или 100 ГБ).


Принципиальное отличие от G1 GC

В сборщике G1 фаза эвакуации (копирование выживших объектов и уплотнение памяти) происходит во время паузы Stop-The-World: чем больше живых объектов в регионе, тем дольше «висит» сервис.

Shenandoah выполняет эвакуацию (перемещение) объектов КОНКУРЕНТНО — параллельно с исполнением бизнес-кода приложения. Потоки продолжают обрабатывать запросы пользователей прямо в тот момент, когда сборщик мусора переносит объекты с одного физического адреса памяти на другой.


Бытовая аналогия

  • G1 GC: Ремонт больничной палаты. Всех пациентов и врачей выставляют в коридор, вешают замок и красят стены (STW).
  • Shenandoah GC: Ремонтники аккуратно переставляют мебель и красят дальний угол палаты прямо во время врачебного обхода. Врачи и пациенты продолжают общаться, практически не замечая ремонтных работ.

🟡 Middle Level

Эволюция барьеров: от Brooks Pointers к Load-Reference Barriers

Главная инженерная сложность конкурентного перемещения: что делать, если поток приложения пытается прочитать или изменить объект ровно в тот момент, когда GC переносит его на новый адрес?

1. Ранняя реализация: Указатели Брукса (Brooks Pointers в Java 12–13)

В заголовок каждого объекта добавлялся дополнительный 8-байтный указатель пересылки (Forwarding Pointer):

  • В покое объект ссылался сам на себя: obj.forwardee = obj.
  • При перемещении сборщик атомарной инструкцией CAS переключал указатель на новую копию: old_obj.forwardee = new_obj.
  • Минус: Каждый объект в куче стал весить на 8 байт больше, увеличивая Footprint памяти на 10–20%.

2. Современная реализация: Load-Reference Barriers (LRB в Java 14+)

Начиная с Java 14, инженеры Red Hat полностью устранили оверхед в 8 байт на объект:

  • Forwarding pointer теперь записывается временно прямо в служебное поле Mark Word заголовка объекта только в момент его эвакуации.
  • JIT вставляет Load-Reference Barrier (LRB) при чтении ссылки: барьер мгновенно определяет, находится ли объект в Collection Set, и возвращает адрес актуальной копии, автоматически обновляя ссылку.

Фазы цикла сборки Shenandoah

1. Init Mark (STW, ~0.5 мс)          ──► Сканирование GC Roots
2. Concurrent Marking               ──► Фоновый обход графа объектов (SATB)
3. Final Mark (STW, ~1 мс)           ──► Завершение разметки, выбор регионов CSet
4. Concurrent Evacuation             ──► ПЕРЕМЕЩЕНИЕ ОБЪЕКТОВ БЕЗ ОСТАНОВКИ ПРИЛОЖЕНИЯ!
5. Init Update Refs (STW, ~0.5 мс)   ──► Подготовка к обновлению ссылок
6. Concurrent Update References      ──► Фоновая перезапись всех старых адресов на новые
7. Final Update Refs (STW, ~0.5 мс)  ──► Обновление ссылок в GC Roots
8. Concurrent Cleanup                ──► Возврат полностью освобожденных регионов

Обратите внимание: Все 4 паузы STW имеют фиксированную константную длительность и не зависят от общего объема кучи!


Сравнительная таблица: Shenandoah против ZGC

Критерий Shenandoah GC ZGC
Разработчик Red Hat (OpenJDK) Oracle
Механизм барьеров Load-Reference Barriers (LRB) Colored Pointers + Load Barriers
Сжатые указатели (Compressed OOPs) Поддерживаются (< 32 ГБ) Не поддерживаются (требует 64 бита)
Память на указатели 4 байта при Heap < 32 ГБ Всегда 8 байт
Совместимость с ОС Отличная (Linux, Alpine musl, Windows, macOS, ARM, x86) Исторически оптимизирован под glibc/Linux x64/AArch64

🔴 Senior Level

Каскад деградации: Degenerated GC и Full GC

Если скорость выделения памяти приложением (Allocation Rate) превышает скорость фоновой эвакуации сборщика, свободная память в куче исчерпывается до завершения цикла:

Нормальный режим (Concurrent)
        │ (Нехватка памяти при эвакуации)
        ▼
   [ Pacing ] ──► Принудительное замедление аллоцирующих потоков
        │ (Память всё еще не освобождена)
        ▼
[ Degenerated GC ] ──► ЭКСТРЕННЫЙ STW: сборщик замораживает JVM и 
        │              доделывает ТЕКУЩУЮ ФАЗУ в режиме Stop-The-World!
        ▼ (Если памяти все равно не хватило)
  [ Full GC ]  ──► Катастрофический однопоточный/многопоточный Mark-Compact
  1. Pacing (Пеacing): Shenandoah на несколько микросекунд притормаживает потоки, которые слишком быстро создают объекты, давая фоновому сборщику фору.
  2. Degenerated GC: Если память закончилась на середине фазы (например, во время Concurrent Evacuation), Shenandoah не сбрасывает работу с нуля. Он ставит мир на паузу (STW) и быстро доделывает оставшиеся 20% перемещения объектов силами всех доступных ядер CPU. Длительность паузы обычно составляет 50–150 мс (в разы быстрее Full GC).
  3. Full GC: Последняя линия обороны, сигнализирующая о критическом дефиците кучи.

Generational Shenandoah (Java 21+)

Аналогично ZGC, в Shenandoah было добавлено разделение на поколения (Generational Mode):

  • Молодые объекты собираются в изолированных регионах гораздо чаще.
  • Эвристика сборщика тратит на 50% меньше процессорного времени (CPU Utilization) на фоновые потоки, снижая риск Degenerated GC практически до нуля.

Производственный тюнинг

# 1. Включение сборщика
-XX:+UseShenandoahGC

# 2. Выбор эвристики (по умолчанию adaptive)
-XX:ShenandoahGCHeuristics=adaptive

# 3. Агрессивный возврат свободной памяти операционной системе
-XX:ShenandoahUncommitDelay=10000

# 4. Логирование деградаций и фаз
-Xlog:gc*,gc+phases=debug:file=/var/log/shenandoah.log:time,uptime,pid:filecount=5,filesize=100M

🎯 Шпаргалка для интервью

30-секундный ответ (Elevator Pitch)

«Shenandoah GC — открытый ultra-low latency сборщик мусора от Red Hat, удерживающий паузы Stop-The-World в диапазоне 1–10 мс независимо от размера кучи. Главное отличие от G1 — конкурентная эвакуация: перемещение объектов и обновление ссылок происходят в фоновых потоках параллельно с исполнением кода приложения с помощью барьеров Load-Reference Barriers (LRB). В отличие от ZGC, Shenandoah поддерживает Compressed OOPs на кучах до 32 ГБ, экономя память на ссылках, и отлично работает в легковесных контейнерах (включая Alpine Linux на musl libc). При экстремальных нагрузках сборщик имеет мягкий fallback в виде Degenerated GC, спасающий от мгновенного Full GC».


3–4 каверзных вопроса с ответами

  1. В чем ключевое архитектурное отличие Shenandoah от G1 GC?
    • Ответ: В G1 фаза эвакуации (копирование выживших объектов из Collection Set в новые регионы) выполняется во время Stop-The-World паузы, поэтому пауза G1 напрямую растет при увеличении объема живых данных. Shenandoah выполняет фазу эвакуации полностью параллельно (Concurrent) с работающим приложением благодаря барьерам Load-Reference Barriers, сводя STW-паузы к микросекундным операциям фиксации корней GC Roots.
  2. Что такое указатели Брукса (Brooks Pointers) и почему от них отказались?
    • Ответ: В ранних версиях Shenandoah каждый объект содержал дополнительное 8-байтное поле в заголовке — указатель пересылки (Brooks Pointer), который изначально указывал на сам объект, а при эвакуации через CAS переключался на новый адрес копии. От них отказались в пользу Load-Reference Barriers (LRB), так как 8 байт на каждый объект давали оверхед до 15–20% памяти кучи. LRB кодирует статус пересылки непосредственно в существующем Mark Word только на момент эвакуации.
  3. Что такое Degenerated GC в Shenandoah?
    • Ответ: Это промежуточный защитный механизм при нехватке памяти во время конкурентной фазы (Evacuation Failure). Вместо немедленного падения в тяжелый Full GC с полной отменой прогресса, Shenandoah объявляет короткий Stop-The-World и доделывает ровно ту фазу, на которой кончилась память (например, домаркировывает или доэвакуирует объекты), задействуя все потоки CPU. После завершения фазы приложение сразу возобновляет работу.
  4. В чем главное преимущество Shenandoah перед ZGC на кучах от 4 до 32 ГБ?
    • Ответ: Shenandoah полноценно поддерживает сжатые указатели Compressed OOPs (-XX:+UseCompressedOops), сохраняя размер ссылок равным 4 байтам. ZGC требует 64-битные указатели для работы Colored Pointers, из-за чего ссылки в нем всегда занимают 8 байт. На кучах до 32 ГБ Shenandoah вмещает на 20–30% больше полезных бизнес-данных и эффективнее использует процессорные кэши L1/L2/L3.

Типичные ошибки и Red Flags

  • 🚩 Утверждение: «Shenandoah использует Colored Pointers» (Colored Pointers использует ZGC; Shenandoah использует Load-Reference Barriers).
  • 🚩 Заблуждение: «Shenandoah не поддерживает сжатые указатели» (Shenandoah отлично работает с Compressed OOPs).
  • 🚩 Утверждение: «Degenerated GC — это то же самое, что Full GC» (Degenerated GC лишь быстро доделывает текущую фазу в режиме STW, а Full GC пересобирает всю кучу с нуля).
  • 🚩 Игнорирование затрат CPU: как и ZGC, Shenandoah отбирает 5–15% процессорного времени на барьеры и фоновую эвакуацию.

Связанные темы