Опишите алгоритм работы сборщика мусора (Garbage Collector) в Go.

Ответ

Сборщик мусора (GC) в Go использует конкурентный трехцветный алгоритм маркировки и очистки (concurrent tricolor mark-and-sweep). Он работает параллельно с основной программой, минимизируя время остановки (Stop-The-World).

Основной алгоритм

Алгоритм делит все объекты в куче на три множества:

  • Белые (White): Потенциальный мусор. Изначально все объекты белые.
  • Серые (Gray): Объекты, которые помечены как достижимые, но на их дочерние объекты еще не было ссылок. Это очередь объектов для сканирования.
  • Черные (Black): Достижимые объекты, все дочерние объекты которых также были просканированы.

Этапы работы

  1. Подготовка (Setup): Короткая пауза (STW) для включения барьера записи (write barrier). Барьер записи — это механизм, который перехватывает все операции записи указателей, чтобы GC не пропустил новые связи между объектами, созданные во время его работы.
  2. Маркировка (Marking):
    • GC начинает сканирование с корневых объектов (глобальные переменные, стеки горутин) и помещает их в серое множество.
    • Далее он итеративно берет объект из серого множества, окрашивает его в черный и сканирует все объекты, на которые он ссылается. Если дочерний объект белый, он окрашивается в серый и добавляется в очередь.
    • Этот этап выполняется конкурентно с выполнением программы.
  3. Завершение маркировки (Mark Termination): Короткая пауза (STW) для завершения маркировки оставшихся объектов и отключения барьера записи.
  4. Очистка (Sweeping):
    • GC проходит по всей куче и освобождает память, занятую всеми объектами, которые остались в белом множестве.
    • Этот этап также выполняется конкурентно.

Ключевые особенности

  • Конкурентность: Большинство фаз GC выполняются параллельно с кодом приложения, что приводит к очень коротким паузам (обычно менее 1 мс).
  • Отсутствие поколений (Non-generational): В отличие от GC в Java или .NET, сборщик мусора в Go не разделяет объекты на поколения (молодые и старые). Это упрощает алгоритм, но может приводить к большему объему работы на каждом цикле.
  • Управление: Можно принудительно запустить GC с помощью runtime.GC() (не рекомендуется в продакшене) и настроить его поведение через переменную окружения GOGC (по умолчанию 100), которая определяет, при каком проценте роста кучи запускать следующий цикл GC.