> Когда очищается память массива под слайсом в Go (Go)

Уровень: middle · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Ozon

Стек: Go

> Пример ответа

Короткий ответ

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

Подробное объяснение

Слайс в Go - это структура из трёх полей: указатель на массив, длина и ёмкость. Сам массив выделяется отдельно в памяти. Время жизни массива не привязано к времени жизни слайса - оно определяется достижимостью объекта через граф ссылок.

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

  1. Помечивание (mark) - GC обходит граф объектов от корней (стек, глобальные переменные, регистры) и помечает достижимые объекты.
  2. Очистка (sweep) - непомеченные объекты считаются мусором, их память возвращается в аллокатор.
  3. Компактирование не выполняется - Go использует non-moving GC, поэтому адреса объектов стабильны.

Важный нюанс: если слайс s ссылается на массив, а затем вы создаёте s2 := s[1:3], оба слайса ссылаются на один и тот же массив. Массив будет освобождён только когда оба слайса станут недостижимы.

Также стоит учитывать, что если вы берёте срез от большого массива и сохраняете маленький слайс, весь массив остаётся в памяти, пока жив этот слайс. Это классическая утечка памяти, если не использовать copy для выделения нового массива.

На практике

На практике важно понимать, что точное время освобождения памяти не детерминировано. GC запускается по порогу роста кучи (по умолчанию 100% от текущего размера) или принудительно через runtime.GC(). Для большинства приложений это не критично, но для latency-sensitive сервисов стоит следить за частотой GC через GODEBUG=gctrace=1 или pprof.

Если нужно гарантированно освободить память раньше, можно:

  • обнулить ссылку на слайс (s = nil), чтобы GC мог собрать массив при следующем цикле;
  • использовать runtime.KeepAlive для продления жизни объекта в специфичных случаях;
  • для больших данных рассмотреть arena (экспериментально в Go 1.20+) или ручное управление через unsafe.

Для избежания утечек при срезах от больших массивов используйте copy:

GO
big := make([]byte, 1<<30)
small := make([]byte, 100)
copy(small, big[:100])
// big может быть собран GC, small не держит ссылку на большой массив

Пример кода

GO
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
// Создаём большой массив через слайс
big := make([]byte, 10<<20) // 10 MB
// Берём маленький срез - он ссылается на тот же массив
small := big[:100]
// Обнуляем big, но small всё ещё держит массив
big = nil
runtime.GC()
// Массив не освобождён, потому что small ссылается на него
fmt.Println("small length:", len(small))
// Теперь обнуляем и small
small = nil
runtime.GC()
// Массив может быть собран при следующем цикле GC
fmt.Println("after GC")
// Пример с copy для избежания удержания большого массива
big2 := make([]byte, 10<<20)
small2 := make([]byte, 100)
copy(small2, big2[:100])
big2 = nil
runtime.GC()
// big2 может быть освобождён, small2 не держит ссылку на него
fmt.Println("small2 length:", len(small2))
time.Sleep(time.Second)
}

Как отвечать на собеседовании

Начните с того, что память освобождает GC, а не сам слайс. Подчеркните, что слайс - это ссылка на массив, и время жизни массива определяется достижимостью. Упомяните, что освобождение происходит не сразу, а по алгоритму GC. Если спросят про конкретные сроки - скажите, что это зависит от настроек рантайма и порога срабатывания GC. Хорошо добавить пример про утечку при срезах от больших массивов и решение через copy. Если интервьюер углубляется, можно рассказать про non-moving GC и отсутствие компактификации.

Что проверяет интервьюер

Интервьюер проверяет:

  • понимание устройства слайса (указатель, длина, ёмкость);
  • знание модели управления памятью в Go (GC, а не ручное освобождение);
  • осознание разницы между временем жизни слайса и массива;
  • знание типичных проблем (удержание большого массива через маленький срез);
  • понимание, что GC не детерминирован и не гарантирует мгновенного освобождения.

Типичные ошибки

  • Утверждение, что память освобождается сразу после выхода слайса из области видимости - это неверно, работает GC.
  • Игнорирование проблемы удержания большого массива через маленький срез.
  • Предложение вызвать free() или delete() - в Go нет ручного освобождения памяти.
  • Путаница между len и cap при объяснении времени жизни.
  • Утверждение, что GC вызывается по таймеру - на самом деле по порогу роста кучи.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью