Ответ
Сборщик мусора (GC) в Go определяет неиспользуемые объекты на основе принципа достижимости (reachability). Объект считается "живым" (достижимым), если до него можно добраться из корневых точек (roots), таких как:
- Глобальные переменные.
- Стеки всех активных горутин (локальные переменные, аргументы функций).
- Регистры процессора.
Если до объекта невозможно добраться, он считается "мусором" и подлежит удалению.
Для реализации этого Go использует конкурентный алгоритм трехцветной маркировки и очистки (concurrent tricolor mark-and-sweep).
Алгоритм трехцветной маркировки
Все объекты в куче условно делятся на три множества (цвета):
- ⚪ Белые объекты (White set): Потенциальный мусор. В начале цикла GC все объекты считаются белыми.
- 🔘 Серые объекты (Gray set): Достижимые объекты, но ссылки из них еще не были просканированы. Это очередь объектов на сканирование.
- ⚫ Черные объекты (Black set): Достижимые объекты, все исходящие из них ссылки проверены и их дочерние объекты помещены в серое множество.
Процесс выглядит так:
- GC начинает с корневых объектов и помещает их в серое множество.
- Затем он берет объект из серого множества, "окрашивает" его в черный и сканирует все объекты, на которые он ссылается.
- Каждый встреченный белый объект окрашивается в серый и добавляется в очередь на сканирование.
- Процесс продолжается, пока серое множество не опустеет.
- В конце цикла все объекты, оставшиеся в белом множестве, являются недостижимым мусором и удаляются.
Ключевые особенности 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 этот объект будет считаться мусором и его память будет освобождена.
}