Как устроена модель памяти в Go?

Ответ

Модель памяти в Go разделена на две основные области: стек (stack) и куча (heap). Управление памятью автоматизировано с помощью сборщика мусора (Garbage Collector, GC).

1. Стек (Stack)

  • Назначение: Быстрая память для хранения локальных переменных, аргументов функций и возвращаемых значений. Память выделяется и освобождается автоматически при входе в функцию и выходе из неё.
  • Особенности:
    • У каждой горутины есть свой собственный, независимый стек.
    • Начальный размер стека мал (~2 КБ), но он может динамически расти и сжиматься по мере необходимости.
    • Доступ к стековой памяти очень быстрый (LIFO - Last-In, First-Out).

2. Куча (Heap)

  • Назначение: Область памяти для хранения данных, которые должны "пережить" вызов функции, или данных, размер которых неизвестен на этапе компиляции. Сюда попадают все значения, которые "ускользают" (escape) из своего локального скоупа.
  • Особенности:
    • Память в куче управляется сборщиком мусора. Он отслеживает, какие объекты всё ещё используются, и освобождает память от неиспользуемых.
    • Выделение памяти в куче медленнее, чем в стеке.

Анализ ускользания (Escape Analysis)

Это процесс, в ходе которого компилятор Go определяет, где выделить память для переменной — в стеке или в куче. Если компилятор доказывает, что переменная не будет использоваться после завершения функции, она размещается в стеке. В противном случае она "ускользает" в кучу.

// x "ускользает" в кучу, так как на неё остаётся ссылка
// за пределами функции.
func createInt() *int {
    x := 42
    return &x 
}

Сборщик мусора (GC)

Go использует конкурентный (concurrent) сборщик мусора, основанный на алгоритме трёхцветной маркировки (tricolor mark-and-sweep). Он работает параллельно с основной программой, минимизируя паузы (stop-the-world pauses) до долей миллисекунды.

new() и make()

  • new(T): Выделяет память для значения типа T, инициализирует его нулевым значением и возвращает указатель *T. Память всегда выделяется в куче.
  • make(T, ...): Используется только для slices, maps и channels. Инициализирует внутренние структуры этих типов и возвращает готовое к использованию значение типа T (не указатель). Память также выделяется в куче.