Расскажите о сборщике мусора (Garbage Collector) в Go: как он работает и каковы его ключевые особенности?

Ответ

В Go используется полностью автоматический сборщик мусора (Garbage Collector, GC), который самостоятельно освобождает память, занятую объектами, ставшими недостижимыми из кода программы (например, локальные переменные после выхода из функции).

Основу GC в Go составляет неблокирующий (concurrent) алгоритм mark-and-sweep (пометь и убери).

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


  1. Concurrent (Параллельный): GC работает параллельно с основной программой (горутинами), а не останавливает её на всё время своей работы. Это приводит к очень коротким паузам (stop-the-world pauses), обычно менее миллисекунды, что критически важно для высоконагруженных и low-latency систем.


  2. Трехцветный алгоритм: Это вариация mark-and-sweep. Объекты в памяти условно окрашиваются в один из трех цветов для отслеживания достижимости:

    • Белый: Потенциальный мусор.
    • Серый: Объект достижим, но его дочерние объекты еще не проверены.
    • Черный: Объект достижим, и все его дочерние объекты проверены.

  3. Управление через GOGC: Поведение GC можно настраивать через переменную окружения GOGC. Она задает процент, на который должна вырасти куча (heap) по сравнению с её размером после прошлой сборки, чтобы запустить новую. По умолчанию GOGC=100, что означает, что новая сборка мусора начнется, когда размер используемой памяти в куче удвоится.


Пример для демонстрации (не для реального кода):
В реальных приложениях вызывать GC вручную не нужно. Код ниже просто показывает, как GC освобождает память.

package main

import (
    "fmt"
    "runtime"
)

func printMemStats() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    // HeapAlloc - это байты, выделенные и все еще используемые.
    fmt.Printf("HeapAlloc = %v MiBn", m.HeapAlloc / 1024 / 1024)
}

func main() {
    printMemStats()

    // Создаем большой объект, который станет мусором после выхода из функции.
    _ = make([]byte, 20<<20) // 20 MB

    printMemStats()

    // Принудительно запускаем GC для демонстрации.
    // В реальном коде это делать не рекомендуется!
    runtime.GC()

    fmt.Println("nAfter GC:")
    printMemStats()
}