← Назад к вопросам

Как Garbage collector понимает, что объект можно уничтожить?

2.0 Middle🔥 191 комментариев
#Основы Go#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принцип работы сборщика мусора (Garbage Collector) в Go

В Go сборщик мусора (GC) использует алгоритм маркировки и очистки (mark-and-sweep) с триколорной (tri-color) маркировкой и параллельной (concurrent) сборкой. Ключевой принцип определения "живых" объектов — достижимость (reachability) через цепочку ссылок от корневых точек (root set).

Ключевые этапы определения достижимости

1. Определение корневых точек (Root Set)

GC начинает с анализа корневых объектов, которые гарантированно доступны:

  • Глобальные переменные
  • Локальные переменные в активных стеках вызовов (горутинах)
  • Указатели в регистрах процессора
  • Специальные структуры runtime (например, runtime.g для горутин)
package main

var globalObj *MyStruct // Корневая точка: глобальная переменная

func main() {
    localObj := &MyStruct{} // Корневая точка: локальная переменная в стеке main
    go process(localObj)    // Корневая точка: аргумент в стеке горутины
}

2. Алгоритм триколорной маркировки

Объекты классифицируются по трем цветам:

  • Черный (black) — объект обработан, все его дочерние указатели просканированы
  • Серый (grey) — объект достижим, но его дочерние указатели еще не проверены
  • Белый (white) — объект необработан или недостижим

Процесс маркировки:

  1. Все объекты изначально белые
  2. Корневые объекты помечаются серыми
  3. Пока есть серые объекты:
    • Берется серый объект, сканируются его указатели
    • Объект становится черным
    • Найденные через него объекты становятся серыми
// Упрощенная иллюстрация цепочки достижимости
type Node struct {
    next *Node
    data []byte
}

func createChain() {
    a := &Node{}        // Корневая точка
    a.next = &Node{}    // Достижим через 'a'
    a.next.next = &Node{} // Достижим через цепочку
    
    // После выхода из функции объекты становятся кандидатами на сборку,
    // если на них нет ссылок извне
}

3. Барьеры памяти (Write Barrier)

Для обеспечения корректности при параллельной сборке Go использует барьеры записи — специальный механизм, который отслеживает изменение указателей во время работы GC. Если программа изменяет указатель на объект во время маркировки, барьер гарантирует, что новый объект будет помечен как достижимый.

// Пример, где барьер важен
var global *Data

func updatePointer() {
    obj := &Data{}
    // Барьер фиксирует, что 'global' теперь указывает на 'obj'
    // Это предотвращает случайную сборку 'obj', если GC уже просканировал 'global'
    global = obj 
}

4. Финальная очистка (Sweep Phase)

После завершения маркировки:

  • Все белые объекты считаются недостижимыми и подлежат освобождению
  • Память возвращается в кучу для повторного использования
  • Черные объекты остаются в памяти

Специфичные оптимизации Go GC

  1. Отслеживание размера стеков горутин — GC анализирует указатели в каждом кадре стека
  2. Нет поколений (generational GC) — В отличие от Java/C#, Go использует непоколенческий GC, но с оптимизациями для короткоживущих объектов через pacing-алгоритм
  3. Смежные битовые карты (bitmaps) — Для эффективного отслеживания указателей в сложных структурах
  4. Параллельная и инкрементальная сборка — GC работает параллельно с программой, минимизируя паузы (STW — Stop-The-World паузы сокращены до микросекунд)

Пример, когда объект становится недостижимым

func createGarbage() {
    // 1. Создаем объект
    data := make([]int, 1000) 
    
    // 2. Перестаем ссылаться на объект
    data = nil 
    
    // 3. В этот момент slice становится кандидатом на сборку
    // (но сборка может произойти позже, а не мгновенно)
    
    // 4. Если где-то сохранили ссылку, объект остается достижимым
    preserveReference(data)
}

func preserveReference(arr []int) {
    // Пустая функция - ссылка теряется при выходе
}

Практические последствия для разработчика

  • Циклические ссылки автоматически удаляются, если на цикл нет внешних ссылок
  • Сборка недетерминирована — GC запускается при определенных условиях (рост кучи, ручной вызов runtime.GC())
  • Мониторинг — использование GODEBUG=gctrace=1 для анализа работы сборщика

Итог: Go GC определяет уничтожаемые объекты через анализ достижимости от корневых точек с использованием параллельной триколорной маркировки, обеспечивая низкие латентности без явного управления памятью со стороны разработчика.

Как Garbage collector понимает, что объект можно уничтожить? | PrepBro