Что такое G1 GC
G1 пришел на смену устаревшему Parallel GC благодаря ключевому свойству: предсказуемости задержек (Predictable Latency). Разработчик может задать желаемую максимальную длительно...
🟢 Junior Level
G1 GC (Garbage-First Garbage Collector) — это серверный сборщик мусора по умолчанию в Java (начиная с Java 9), спроектированный для многопроцессорных систем с большими объемами оперативной памяти (от 4 ГБ до десятков гигабайт).
G1 пришел на смену устаревшему Parallel GC благодаря ключевому свойству: предсказуемости задержек (Predictable Latency). Разработчик может задать желаемую максимальную длительность паузы Stop-The-World с помощью параметра:
-XX:MaxGCPauseMillis=200 # Целевая максимальная пауза 200 мс (по умолчанию)
Почему он называется «Garbage-First»?
В традиционных сборщиках поколения памяти были непрерывными физическими блоками. G1 разбивает всю кучу на тысячи равных независимых ячеек — регионов.
В процессе работы G1 непрерывно отслеживает процент мусора в каждом регионе. Когда наступает время сборки, он в первую очередь очищает те регионы, где больше всего мусора («Garbage-First»), обеспечивая максимальное высвобождение памяти за минимальное время.
Бытовая аналогия
- Традиционный GC: Уборщица пытается за один присест вымыть все полы в 50-этажном офисном здании. Здание перекрывают на 3 часа (долгий Stop-The-World).
- G1 GC: Уборщица знает, что у нее есть строго 15 минут перерыва. Она заходит только в те 3 кабинета, где сегодня праздновали день рождения и разлили кофе (Garbage-First), быстро наводит там порядок и возвращается к работе, укладываясь в регламент.
🟡 Middle Level
Региональная структура кучи в G1
Вся оперативная память кучи (Java Heap) делится на ~2048 одинаковых регионов размером от 1 МБ до 32 МБ (размер всегда равен степени двойки и вычисляется автоматически при старте JVM):
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐
│ E │ S │ O │ F │ E │ H │ H │ O │
│ (Eden)│ (Surv)│ (Old) │(Free) │ (Eden)│ (Hum) │ (Hum) │ (Old) │
└───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
Роли регионов динамичны:
- E (Eden): Регионы для размещения новых объектов.
- S (Survivor): Регионы для объектов, переживших Minor GC.
- O (Old): Регионы для долгоживущих объектов.
- H (Humongous): Непрерывная цепочка регионов для размещения гигантских объектов (размером $> 50\%$ от размера региона).
- F (Free): Пул свободных регионов.
Важно: Регионы одного поколения не обязаны быть смежными! Регион, который сегодня был Eden, после сборки мусора очищается и завтра может стать Old или Survivor.
Ключевые структуры: CSet против RSet
Понимание разницы между CSet и RSet — самый частый вопрос на собеседованиях уровня Middle+:
┌────────────────────────────────────────────────────────┐
│ 1. Collection Set (CSet) — ЧТО СОБИРАЕМ: │
│ Набор регионов, выбранных для эвакуации (очистки) │
│ в текущей паузе. При Young GC CSet состоит из всех │
│ Eden и Survivor регионов. При Mixed GC в CSet │
│ добавляются самые загрязненные регионы Old Gen. │
├────────────────────────────────────────────────────────┤
│ 2. Remembered Set (RSet) — ОТКУДА ССЫЛАЮТСЯ: │
│ Персональная структура ДЛЯ КАЖДОГО РЕГИОНА. │
│ Хранит список внешних регионов, которые ссылаются │
│ на объекты внутри текущего региона. │
│ Благодаря RSet сборщику не нужно сканировать всю │
│ кучу, чтобы собрать один изолированный регион! │
└────────────────────────────────────────────────────────┘
Полный цикл сборки мусора в G1
- Young GC (STW): По мере заполнения регионов Eden их живые объекты эвакуируются (копируются) в свободные регионы Survivor или Old.
- Concurrent Marking Cycle (Фоновая разметка):
- Initial Mark (STW): Размечаются корни GC Roots (совмещается с Young GC).
- Concurrent Mark: Фоновый обход графа объектов параллельно с работой приложения по алгоритму SATB (Snapshot-At-The-Beginning).
- Remark (STW): Финализация разметки, опустошение буферов SATB.
- Cleanup (STW): Полностью пустые регионы возвращаются в пул Free.
- Mixed GC (Смешанные сборки): Очищается Young Gen плюс пачка самых «грязных» регионов Old Gen. Выполняется серией из нескольких коротких сборок (по умолчанию до 8 циклов).
🔴 Senior Level
Авария эвакуации: Evacuation Failure (to-space exhausted)
Самая опасная ситуация в G1:
- Сборщик пытается скопировать живой объект из региона CSet, но в пуле свободных регионов (Free regions) нет места.
- В логах появляется маркер:
(to-space exhausted)илиEvacuation Failure. - Что делает JVM: Сборщик экстренно останавливает эвакуацию, разворачивает ссылки обратно и инициирует Full GC. До Java 10 Full GC в G1 был однопоточным и приводил к остановкам на 30–60 секунд. Начиная с Java 10, Full GC стал параллельным (
Parallel Full GC), но все равно остается неприемлемым для production с жестким SLA.
Крупные объекты (Humongous Allocations)
Если размер объекта превышает 50% от размера региона G1, он признается Humongous:
- Аллоцируется напрямую в цепочку непрерывных регионов Old Generation.
- Humongous-объекты не эвакуируются (не копируются), что исключает накладные расходы на их перемещение.
- Опасность: Частые аллокации больших массивов вызывают фрагментацию пула свободных регионов. Если для массива размером 10 МБ не найдется 3 непрерывных свободных региона по 4 МБ, G1 немедленно свалится в Full GC.
- Решение: Явное увеличение размера региона:
-XX:G1HeapRegionSize=16mили32m.
Дедупликация строк: String Deduplication
Флаг -XX:+UseStringDeduplication (доступен в G1 с Java 8u20):
- Во время фазы эвакуации фоновый поток G1 сканирует строковые объекты, пережившие несколько сборок.
- Если сборщик находит два разных объекта
Stringс одинаковым содержимым, он перепривязывает внутреннее полеbyte[] valueу второй строки на массив первой строки. - Дублирующий массив байт удаляется сборщиком.
- Экономит от 10% до 25% кучи в enterprise-приложениях без изменения бизнес-кода.
Практический тюнинг G1 в Production
# 1. Задаем целевую паузу (не ставить ниже 50-100 мс!)
-XX:MaxGCPauseMillis=200
# 2. Включаем адаптивный расчет старта разметки (включен по умолчанию с Java 9)
-XX:+G1UseAdaptiveIHOP
# 3. Увеличиваем размер региона при частых Humongous allocations
-XX:G1HeapRegionSize=16m
# 4. Включаем дедупликацию строк
-XX:+UseStringDeduplication
# 5. Детальное логирование GC (Java 9+)
-Xlog:gc*,gc+phases=debug:file=/var/log/app-gc.log:time,uptime,pid:filecount=5,filesize=100M
⚠️ Главное правило тюнинга G1: НИКОГДА не устанавливайте флаг
-Xmn! Фиксация размера Young Generation намертво лишает G1 возможности сжимать и расширять Eden для удержания времени паузы в рамкахMaxGCPauseMillis.
🎯 Шпаргалка для интервью
30-секундный ответ (Elevator Pitch)
«G1 GC — региональный сборщик мусора по умолчанию в Java 9+, сочетающий высокую пропускную способность и предсказуемые задержки. Куча разбивается на ~2048 динамических регионов (от 1 до 32 МБ). Сборщик очищает память инкрементально, выбирая регионы с наибольшим количеством мусора («Garbage-First»), чтобы уложиться в целевую паузу
-XX:MaxGCPauseMillis(по умолчанию 200 мс). Входящие ссылки между регионами отслеживаются структурами RSet, а целевой набор очистки формируется в CSet. Конкурентная разметка выполняется по алгоритму SATB, а очистка Old Gen происходит порциями во время Mixed GC».
3–4 каверзных вопроса с ответами
- Почему сборщик мусора называется «Garbage-First»?
- Ответ: Во время фоновой фазы разметки G1 определяет точный объем живых и мертвых данных в каждом регионе кучи. На фазе Mixed GC сборщик сортирует регионы Old Generation по плотности мусора и берет в обработку в первую очередь те регионы, где мусора больше всего. Это гарантирует максимальный объем освобожденной памяти за отведенный бюджет времени паузы.
- В чем разница между Collection Set (CSet) и Remembered Set (RSet)?
- Ответ: CSet (Collection Set) — это набор регионов, которые выбраны для эвакуации прямо сейчас в текущей паузе сборки мусора (все живые объекты из них будут перемещены, а сами регионы очищены). RSet (Remembered Set) — это вспомогательная структура данных, существующая у каждого региона, которая хранит ссылки на него из всех других регионов кучи. RSet позволяет очистить любой отдельный регион без необходимости полного сканирования остальной кучи.
- Что происходит при возникновении ошибки Evacuation Failure (
to-space exhausted)?- Ответ: Это аварийное состояние, когда во время Minor или Mixed GC сборщик мусора копирует выжившие объекты, но обнаруживает, что свободных регионов в куче больше нет. G1 отменяет перемещение оставшихся объектов, фиксирует их на текущих местах и запускает глобальный Stop-The-World Full GC для всей памяти. Это приводит к длительному зависанию сервиса и указывает на необходимость увеличения кучи или оптимизации генерации мусора.
- Почему при использовании G1 категорически нельзя фиксировать размер Young Generation флагом
-Xmn?- Ответ: Главная сила G1 — адаптивность. Чтобы выдерживать целевое время паузы
MaxGCPauseMillis, сборщик перед каждым циклом динамически меняет количество регионов, выделенных под Young Generation (от 5% до 60% кучи). Если принудительно задать-Xmn, размер Young Gen становится жесткой константой, эвристический алгоритм G1 отключается, и JVM больше не может контролировать время пауз, что приводит к непредсказуемым задержкам.
- Ответ: Главная сила G1 — адаптивность. Чтобы выдерживать целевое время паузы
Типичные ошибки и Red Flags
- 🚩 Установка флага
-Xmnвместе с G1 GC (ломает весь механизм адаптивного управления паузами). - 🚩 Выставление нереалистично маленькой целевой паузы
-XX:MaxGCPauseMillis=10(G1 не успеет собрать мусор, куча переполнится и возникнет Full GC). - 🚩 Путаница между структурами CSet (что собираем) и RSet (кто ссылается).
- 🚩 Игнорирование проблемы Humongous-объектов при обработке тяжелых входящих массивов данных.
Связанные темы
- Что такое поколения в GC — поколения внутри регионов G1
- Что такое ZGC — сравнение G1 со сверхбыстрым ZGC
- Что такое stop-the-world — паузы в циклах сборки
- Какие GC минимизируют stop-the-world паузы — сравнительный анализ low-latency GC
- Что такое Old Generation (Tenured) — специфика сборок Mixed GC