По какому принципу сборщик мусора (Garbage Collector) в Go определяет, что объект больше не используется и его можно удалить?

Ответ

Сборщик мусора (GC) в Go определяет неиспользуемые объекты на основе принципа достижимости (reachability). Объект считается "живым" (достижимым), если до него можно добраться из корневых точек (roots), таких как:

  • Глобальные переменные.
  • Стеки всех активных горутин (локальные переменные, аргументы функций).
  • Регистры процессора.

Если до объекта невозможно добраться, он считается "мусором" и подлежит удалению.

Для реализации этого Go использует конкурентный алгоритм трехцветной маркировки и очистки (concurrent tricolor mark-and-sweep).

Алгоритм трехцветной маркировки

Все объекты в куче условно делятся на три множества (цвета):

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

Процесс выглядит так:

  1. GC начинает с корневых объектов и помещает их в серое множество.
  2. Затем он берет объект из серого множества, "окрашивает" его в черный и сканирует все объекты, на которые он ссылается.
  3. Каждый встреченный белый объект окрашивается в серый и добавляется в очередь на сканирование.
  4. Процесс продолжается, пока серое множество не опустеет.
  5. В конце цикла все объекты, оставшиеся в белом множестве, являются недостижимым мусором и удаляются.

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

  • Конкурентность: Большая часть работы GC (маркировка и очистка) выполняется конкурентно с основной программой, что минимизирует паузы "Stop The World" (STW).
  • Циклические ссылки: Этот алгоритм легко справляется с циклическими ссылками. Если на группу связанных объектов нет ссылок извне, вся группа останется "белой" и будет удалена.
  • Управление: Поведением GC можно управлять с помощью переменной окружения GOGC. Она задает процент роста кучи, который запускает новый цикл сборки мусора (по умолчанию GOGC=100, т.е. сборка начнется, когда размер кучи удвоится по сравнению с прошлым циклом).
// Простой пример
func createObject() *int {
    obj := new(int) // obj создается в куче
    return obj      // ссылка на obj возвращается
}

func main() {
    a := createObject() // a ссылается на объект
    // ... какая-то работа с 'a'
    a = nil             // Ссылка на объект потеряна. Теперь он недостижим.
    // На следующем цикле GC этот объект будет считаться мусором и его память будет освобождена.
}