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

Ответ

Диагностика утечек памяти в Go — это систематический процесс, основанный на использовании встроенного инструментария.

Основные причины утечек памяти в Go:

  1. Незавершённые горутины (goroutine leaks): Горутины, которые блокируются навсегда (например, при чтении из пустого канала), не освобождают свой стек и связанные с ним ресурсы.
  2. Бесконтрольно растущие коллекции: Срезы (slice) или карты (map), которые используются как глобальные кэши или буферы и постоянно растут без очистки.
  3. Незакрытые ресурсы: Забытые defer file.Close() или defer resp.Body.Close() приводят к утечке файловых дескрипторов и памяти.
  4. Ссылки в замыканиях: Переменные, "захваченные" долгоживущими замыканиями, не могут быть собраны сборщиком мусора.

Инструменты и процесс диагностики:

Основной инструмент для анализа памяти — это встроенный профилировщик pprof.

Шаг 1: Подключение pprof к приложению

Для веб-серверов это делается очень просто:

import (
    _ "net/http/pprof" // Анонимный импорт для регистрации обработчиков
    "net/http"
    "log"
)

func main() {
    // ... ваш основной код

    // Запускаем pprof сервер на отдельном порту
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // ...
}

Шаг 2: Сбор профиля памяти (heap profile)

Когда приложение поработало и, предположительно, "накопило" лишнюю память, можно снять профиль:

# Снимаем профиль использования памяти (in-use space)
go tool pprof http://localhost:6060/debug/pprof/heap

Шаг 3: Анализ профиля

После запуска команды вы попадаете в интерактивную консоль pprof. Основные команды:

  • top: Показывает функции, которые удерживают больше всего памяти.
  • list <имя_функции>: Показывает исходный код функции с указанием, на какой строке выделяется память.
  • web: Генерирует и открывает в браузере визуальный граф вызовов. Это самый наглядный способ найти источник утечки.

Альтернативные способы:

  • Тесты с флагом -memprofile:

    go test -memprofile=mem.out ./...
    go tool pprof mem.out

    Это помогает находить утечки на ранних этапах, в рамках CI/CD.

  • runtime.ReadMemStats: Позволяет программно получать базовую статистику о памяти (Alloc, HeapObjects и т.д.) и отправлять её в систему мониторинга для отслеживания динамики потребления памяти со временем.