Що таке 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
- Pacing: Shenandoah на кілька мікросекунд пригальмовує потоки, які занадто швидко створюють об’єкти, даючи фоновому збирачу фору.
- Degenerated GC: Якщо пам’ять закінчилася посеред фази (наприклад, під час Concurrent Evacuation), Shenandoah не скидає роботу з нуля. Він ставить світ на паузу (STW) і швидко завершує залишок переміщення об’єктів силами всіх доступних ядер CPU. Тривалість паузи зазвичай становить 50–150 мс (у рази швидше за Full GC).
- 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 каверзних питання з відповідями
- У чому ключова архітектурна відмінність Shenandoah від G1 GC?
- Відповідь: У G1 фаза евакуації (копіювання вцілілих об’єктів з Collection Set у нові регіони) виконується під час Stop-The-World паузи, тому тривалість паузи G1 безпосередньо зростає зі збільшенням обсягу живих даних. Shenandoah виконує фазу евакуації повністю паралельно (Concurrent) із працюючим застосунком завдяки бар’єрам Load-Reference Barriers, зводячи STW-паузи до мікросекундних операцій фіксації коренів GC Roots.
- Що таке покажчики Брукса (Brooks Pointers) і чому від них відмовилися?
- Відповідь: У ранніх версіях Shenandoah кожен об’єкт містив додаткове 8-байтне поле в заголовку — покажчик пересилання (Brooks Pointer), який спочатку вказував на сам об’єкт, а під час евакуації через CAS перемикався на нову адресу копії. Від них відмовилися на користь Load-Reference Barriers (LRB), оскільки 8 байтів на кожен об’єкт створювали оверхед до 15–20% пам’яті купи. LRB кодує статус пересилання безпосередньо в існуючому полі Mark Word лише на час евакуації.
- Що таке Degenerated GC у Shenandoah?
- Відповідь: Це проміжний захисний механізм у разі нестачі пам’яті під час конкурентної фази (Evacuation Failure). Замість негайного падіння у важкий Full GC з повним скасуванням усього прогресу, Shenandoah оголошує коротку паузу Stop-The-World і доробляє саме ту фазу, на якій закінчилася пам’ять (наприклад, дозавершує маркування або доевакуйовує об’єкти), задіюючи всі потоки CPU. Після завершення фази застосунок одразу відновлює конкурентну роботу.
- У чому головна перевага Shenandoah перед ZGC на купах від 4 до 32 ГБ?
- Відповідь: Shenandoah повноцінно підтримує стиснуті покажчики Compressed OOPs (
-XX:+UseCompressedOops), зберігаючи розмір посилань рівним 4 байтам. ZGC вимагає 64-бітні покажчики для роботи Colored Pointers, через що посилання в ньому завжди займають 8 байтів. На купах до 32 ГБ Shenandoah вміщує на 20–30% більше корисних бізнес-даних та ефективніше утилізує процесорні кеші L1/L2/L3.
- Відповідь: Shenandoah повноцінно підтримує стиснуті покажчики Compressed OOPs (
Типові помилки та 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% процесорного часу на бар’єри та фонову евакуацію.
Пов’язані теми
- Що таке ZGC — порівняння зі збирачем від Oracle
- Що таке G1 GC — відмінність від регіонального збирача G1
- Які алгоритми GC існують — класифікація алгоритмів
- Що таке stop-the-world — влаштування безпечних точок
- Які GC мінімізують stop-the-world паузи — матриця вибору low-latency GC