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

Что такое 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

  1. Young GC (STW): По мере заполнения регионов Eden их живые объекты эвакуируются (копируются) в свободные регионы Survivor или Old.
  2. Concurrent Marking Cycle (Фоновая разметка):
    • Initial Mark (STW): Размечаются корни GC Roots (совмещается с Young GC).
    • Concurrent Mark: Фоновый обход графа объектов параллельно с работой приложения по алгоритму SATB (Snapshot-At-The-Beginning).
    • Remark (STW): Финализация разметки, опустошение буферов SATB.
    • Cleanup (STW): Полностью пустые регионы возвращаются в пул Free.
  3. 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 каверзных вопроса с ответами

  1. Почему сборщик мусора называется «Garbage-First»?
    • Ответ: Во время фоновой фазы разметки G1 определяет точный объем живых и мертвых данных в каждом регионе кучи. На фазе Mixed GC сборщик сортирует регионы Old Generation по плотности мусора и берет в обработку в первую очередь те регионы, где мусора больше всего. Это гарантирует максимальный объем освобожденной памяти за отведенный бюджет времени паузы.
  2. В чем разница между Collection Set (CSet) и Remembered Set (RSet)?
    • Ответ: CSet (Collection Set) — это набор регионов, которые выбраны для эвакуации прямо сейчас в текущей паузе сборки мусора (все живые объекты из них будут перемещены, а сами регионы очищены). RSet (Remembered Set) — это вспомогательная структура данных, существующая у каждого региона, которая хранит ссылки на него из всех других регионов кучи. RSet позволяет очистить любой отдельный регион без необходимости полного сканирования остальной кучи.
  3. Что происходит при возникновении ошибки Evacuation Failure (to-space exhausted)?
    • Ответ: Это аварийное состояние, когда во время Minor или Mixed GC сборщик мусора копирует выжившие объекты, но обнаруживает, что свободных регионов в куче больше нет. G1 отменяет перемещение оставшихся объектов, фиксирует их на текущих местах и запускает глобальный Stop-The-World Full GC для всей памяти. Это приводит к длительному зависанию сервиса и указывает на необходимость увеличения кучи или оптимизации генерации мусора.
  4. Почему при использовании G1 категорически нельзя фиксировать размер Young Generation флагом -Xmn?
    • Ответ: Главная сила G1 — адаптивность. Чтобы выдерживать целевое время паузы MaxGCPauseMillis, сборщик перед каждым циклом динамически меняет количество регионов, выделенных под Young Generation (от 5% до 60% кучи). Если принудительно задать -Xmn, размер Young Gen становится жесткой константой, эвристический алгоритм G1 отключается, и JVM больше не может контролировать время пауз, что приводит к непредсказуемым задержкам.

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

  • 🚩 Установка флага -Xmn вместе с G1 GC (ломает весь механизм адаптивного управления паузами).
  • 🚩 Выставление нереалистично маленькой целевой паузы -XX:MaxGCPauseMillis=10 (G1 не успеет собрать мусор, куча переполнится и возникнет Full GC).
  • 🚩 Путаница между структурами CSet (что собираем) и RSet (кто ссылается).
  • 🚩 Игнорирование проблемы Humongous-объектов при обработке тяжелых входящих массивов данных.

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