Для чего нужен Garbage Collector в Go?

Ответ

Garbage Collector (GC) в Go автоматически управляет памятью, освобождая неиспользуемые объекты в куче (heap). Это избавляет разработчика от ручного управления памятью (как в C/C++), предотвращая утечки памяти и ошибки, связанные с неправильным освобождением.

Основные особенности GC в Go:

  • Неблокирующий (Concurrent): Большая часть работы GC выполняется параллельно с выполнением основной программы, минимизируя паузы (stop-the-world).
  • Триколорная маркировка: Использует алгоритм маркировки объектов (белый, серый, чёрный) для определения достижимых объектов.
  • Короткие "stop-the-world" паузы: Хотя GC в Go в основном конкурентный, короткие фазы "stop-the-world" (когда выполнение программы приостанавливается) всё же присутствуют, но их длительность обычно измеряется микросекундами.
  • Настраивается через GOGC: Переменная окружения GOGC позволяет контролировать, насколько сильно должна вырасти куча перед запуском следующего цикла сборки мусора (по умолчанию 100%).

Пример утечки памяти (если бы GC отсутствовал или был неэффективен):

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    var data []*int
    fmt.Println("Начало работы, нажмите Ctrl+C для выхода.")

    for i := 0; ; i++ {
        // В реальной ситуации, если бы GC не работал, или мы бы не освобождали
        // память явно, этот цикл приводил бы к постоянному росту потребления памяти.
        // В Go GC автоматически освобождает неиспользуемые объекты.
        // Однако, если мы постоянно сохраняем ссылки на объекты, они не будут собраны.
        val := i // Создаем новую переменную на каждой итерации
        data = append(data, &val) // Сохраняем указатель на 'val'

        if i%100000 == 0 {
            fmt.Printf("Итерация %d, количество элементов: %d, Память: %d MBn", i, len(data), getMemUsageMB())
            // В этом примере 'data' постоянно растет, и GC не может освободить 'val',
            // так как на нее есть ссылка. Это демонстрирует, как можно создать
            // логическую утечку, даже при наличии GC.
        }
        time.Sleep(time.Millisecond) // Небольшая задержка для наглядности
    }
}

func getMemUsageMB() uint64 {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    return m.Alloc / 1024 / 1024
}

GC особенно важен для долгоживущих сервисов (например, веб-серверов, микросервисов) и при работе с большими или динамически изменяющимися структурами данных, где ручное управление памятью было бы сложным и подверженным ошибкам.