Какие основные области памяти используются в Go и в чем их различие?

Ответ

В Go, как и во многих других компилируемых языках, для выполнения программы используются две основные области памяти: стек (stack) и куча (heap).

Стек (Stack)

  • Назначение: Хранение локальных переменных функций, аргументов и возвращаемых значений.
  • Принцип работы: Работает по принципу LIFO (Last-In, First-Out). При вызове функции для неё создаётся новый фрейм (stack frame) на вершине стека. При выходе из функции её фрейм удаляется.
  • Скорость: Очень быстрый, так как выделение и освобождение памяти — это простое смещение указателя стека.
  • Размер: Размер стека для каждой горутины изначально небольшой (около 2KB), но может динамически расти при необходимости.
  • Управление: Память управляется автоматически и не требует участия сборщика мусора (GC).

Куча (Heap)

  • Назначение: Хранение данных, время жизни которых не связано с временем выполнения одной функции. Это могут быть глобальные переменные или данные, на которые есть указатели, "пережившие" функцию, в которой они были созданы.
  • Принцип работы: Это большая область памяти, где блоки выделяются по запросу в произвольном порядке.
  • Скорость: Выделение и освобождение памяти в куче — более медленная операция по сравнению со стеком.
  • Размер: Ограничен только доступной оперативной памятью системы.
  • Управление: Памятью в куче управляет сборщик мусора (GC), который периодически находит и освобождает неиспользуемые объекты.

Escape Analysis (Анализ "убегания")

Компилятор Go выполняет escape analysis, чтобы определить, где разместить переменную — в стеке или в куче. Переменная "убегает" в кучу, если компилятор не может доказать, что её использование безопасно ограничено рамками одной функции.

Примеры, когда переменная попадает в кучу:

  1. Возврат указателя на локальную переменную.
  2. Переменная используется в замыкании (closure), которое переживает функцию.
  3. Размер переменной неизвестен на этапе компиляции (например, срез, длина которого определяется в рантайме).
package main

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

func main() {
    y := 10 // 'y' будет размещена в стеке функции main
    ptr := createInt() // 'ptr' содержит адрес в куче
    println(y, *ptr)
}