Ответ
Сборщик мусора (GC) в Go использует конкурентный трехцветный алгоритм маркировки и очистки (concurrent tricolor mark-and-sweep). Он работает параллельно с основной программой, минимизируя время остановки (Stop-The-World).
Основной алгоритм
Алгоритм делит все объекты в куче на три множества:
- Белые (White): Потенциальный мусор. Изначально все объекты белые.
- Серые (Gray): Объекты, которые помечены как достижимые, но на их дочерние объекты еще не было ссылок. Это очередь объектов для сканирования.
- Черные (Black): Достижимые объекты, все дочерние объекты которых также были просканированы.
Этапы работы
- Подготовка (Setup): Короткая пауза (STW) для включения барьера записи (write barrier). Барьер записи — это механизм, который перехватывает все операции записи указателей, чтобы GC не пропустил новые связи между объектами, созданные во время его работы.
- Маркировка (Marking):
- GC начинает сканирование с корневых объектов (глобальные переменные, стеки горутин) и помещает их в серое множество.
- Далее он итеративно берет объект из серого множества, окрашивает его в черный и сканирует все объекты, на которые он ссылается. Если дочерний объект белый, он окрашивается в серый и добавляется в очередь.
- Этот этап выполняется конкурентно с выполнением программы.
- Завершение маркировки (Mark Termination): Короткая пауза (STW) для завершения маркировки оставшихся объектов и отключения барьера записи.
- Очистка (Sweeping):
- GC проходит по всей куче и освобождает память, занятую всеми объектами, которые остались в белом множестве.
- Этот этап также выполняется конкурентно.
Ключевые особенности
- Конкурентность: Большинство фаз GC выполняются параллельно с кодом приложения, что приводит к очень коротким паузам (обычно менее 1 мс).
- Отсутствие поколений (Non-generational): В отличие от GC в Java или .NET, сборщик мусора в Go не разделяет объекты на поколения (молодые и старые). Это упрощает алгоритм, но может приводить к большему объему работы на каждом цикле.
- Управление: Можно принудительно запустить GC с помощью
runtime.GC()
(не рекомендуется в продакшене) и настроить его поведение через переменную окруженияGOGC
(по умолчанию100
), которая определяет, при каком проценте роста кучи запускать следующий цикл GC.