Ответ
Основные причины утечек и высокого потребления памяти в Go — это удержание ссылок на объекты, которые больше не нужны. Вот наиболее частые сценарии:
-
Глобальные переменные и кэши без очистки. Коллекции (map, slice), объявленные в глобальной области видимости, никогда не будут очищены сборщиком мусора (GC), если из них не удалять элементы.
// Кэш будет расти бесконечно, если его не очищать var globalCache = make(map[string]*BigObject)
-
"Зависшие" (leaked) горутины. Горутина, заблокированная навсегда (например, при чтении из канала, в который никто никогда не напишет), удерживает в памяти свой стек и все связанные с ним объекты.
go func() { <-time.After(time.Hour) // Горутина "уснет" на час, удерживая память }() go func() { select {} // Эта горутина заблокируется навсегда }()
-
Неправильная работа со срезами (slices). Срез, являющийся частью большого базового массива, не дает GC освободить этот массив, даже если сам срез небольшой.
bigSlice := make([]byte, 1024*1024) // 1MB // ... какая-то работа smallSlice := bigSlice[:10] // smallSlice использует тот же базовый массив // Теперь 1MB памяти не будет освобожден, пока существует smallSlice
Решение: скопировать нужные данные в новый срез:
correctSlice := make([]byte, 10); copy(correctSlice, bigSlice[:10])
. -
Замыкания, удерживающие ссылки. Горутины или другие структуры могут через замыкание удерживать ссылки на переменные, которые должны были быть освобождены.
-
Использование
defer
в длинных циклах. Операторdefer
выполняет вызов только при выходе из функции. Внутри цикла это может привести к накоплению ресурсов (файловых дескрипторов, памяти), которые освободятся только по завершении всей функции.
Диагностика: лучший способ найти утечки — профилирование с помощью pprof
. Основные инструменты:
go tool pprof -http=:8080 http://.../debug/pprof/heap
— для анализа распределения памяти в куче.go tool pprof http://.../debug/pprof/goroutine
— для поиска зависших горутин.