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

Ответ

Утечки памяти в Go — это ситуации, когда сборщик мусора не может освободить память, так как на неё всё ещё существуют достижимые ссылки, хотя логически программа её больше не использует. Основные источники утечек:

  1. Незавершённые горутины (Goroutine Leaks) Это самая частая причина. Горутина, которая блокируется навсегда (например, при чтении из пустого канала), никогда не завершится, и все переменные, на которые она ссылается, останутся в памяти.

    // Горутина заблокируется навсегда, если в ch никто не запишет
    ch := make(chan int)
    go func() {
        val := <-ch // Утечка: горутина и её стек не будут освобождены
        fmt.Println(val)
    }()
    // time.Sleep(1 * time.Hour) // Горутина "зависла"
  2. Глобальные или долгоживущие коллекции Объекты, добавленные в глобальные или пакетные переменные (мапы, слайсы), не будут собраны GC, пока сама коллекция достижима. Если такая коллекция бесконтрольно растёт, это приводит к утечке.

    // Если кэш только растёт и никогда не очищается, это утечка.
    var globalCache = make(map[string]*User)
  3. Срезы, ссылающиеся на большие базовые массивы Когда вы создаёте небольшой срез из очень большого, новый срез сохраняет ссылку на весь исходный массив. Пока срез жив, сборщик мусора не может освободить память из-под всего массива.

    bigSlice := make([]byte, 10<<20) // 10MB
    // ... обработка ...
    
    // smallSlice удерживает в памяти все 10MB, хотя использует лишь 10 байт
    smallSlice := bigSlice[0:10]
    
    // Решение: скопировать нужные данные в новый срез
    correctSlice := make([]byte, 10)
    copy(correctSlice, bigSlice[0:10])
  4. Незакрытые таймеры и тикеры Объекты time.Timer и time.Ticker содержат внутреннюю горутину и канал. Если их не остановить с помощью метода Stop(), они будут жить вечно.

    ticker := time.NewTicker(1 * time.Minute)
    // Если забыть вызвать ticker.Stop(), горутина тикера будет работать вечно
  5. Вызовы CGO Память, выделенная в C-коде через C.malloc, не управляется сборщиком мусора Go. Её необходимо освобождать вручную с помощью C.free, иначе она утечёт.

Для диагностики утечек в Go основным инструментом является pprof, который позволяет анализировать профили памяти (heap profile) и горутин.