🧠 Розділ 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 байтів більше, збільшуючи Memory 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: Shenandoah на кілька мікросекунд пригальмовує потоки, які занадто швидко створюють об’єкти, даючи фоновому збирачу фору.
  2. Degenerated GC: Якщо пам’ять закінчилася посеред фази (наприклад, під час Concurrent Evacuation), Shenandoah не скидає роботу з нуля. Він ставить світ на паузу (STW) і швидко завершує залишок переміщення об’єктів силами всіх доступних ядер 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% процесорного часу на бар’єри та фонову евакуацію.

Пов’язані теми