Перейти к основному содержимому

Утечка памяти в мапах (Map Memory Leak)

Суть проблемы

Представь, что мы создали мапу, положили в нее 1 миллион элементов, а затем удалили их все в цикле с помощью функции delete().

Вопрос: Освободится ли оперативная память после отработки Garbage Collector (GC)? Ответ: НЕТ!

Почему так происходит?

Как мы разбирали ранее, мапа состоит из массива корзин (buckets).

  • Когда мапа растет, Go выделяет новую память и удваивает количество корзин.
  • Но мапа в Go никогда не уменьшается (never shrinks)!
  • Функция delete(m, key) просто находит нужную ячейку в корзине и помечает её специальным флагом empty. Сама память, выделенная под массив корзин, остается зарезервированной за этой мапой навсегда (пока жива сама переменная мапы).

Нюанс с Go 1.21+: В новых версиях Go появилась встроенная функция clear(m), которая удаляет все элементы из мапы одним разом. Но она тоже не освобождает память! Она лишь очищает корзины, оставляя аллоцированный массив того же размера.

Как правильно очистить память?

Если твоя программа периодически кэширует огромные объемы данных в мапу, а потом удаляет их, у тебя возникнет утечка памяти.

Решений всего два:

  1. Пересоздание мапы (Рекомендуемый способ). Вместо удаления ключей, создай новую мапу, скопируй в нее только нужные (оставшиеся) элементы, а старую мапу занули (m = nil). Тогда GC увидит, что на старый огромный массив корзин больше нет ссылок, и удалит его из памяти.

    // Была огромная мапа m
    newMap := make(map[string]int)
    for k, v := range m {
    if isValid(k) { // Копируем только то, что нужно сохранить
    newMap[k] = v
    }
    }
    m = newMap // Старая мапа отправляется на съедение GC
  2. Хранение указателей. Если значения в мапе — это огромные структуры (например, по 1 Мегабайту каждая), храни в мапе указатели на них: map[string]*BigStruct. При delete из мапы удалится только указатель (8 байт). Сама огромная структура останется без ссылок и будет очищена GC. Базовый массив мапы по-прежнему не сожмется, но он будет хранить только пустые слоты по 8 байт, а не мегабайты данных.