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

Ошибка, при которой программа занимает память и не освобождает ее, хотя данные больше не нужны: со временем приложение тормозит или падает.

Утечка памяти — ошибка, при которой программа выделяет память под данные, а когда они перестают быть нужны, не освобождает ее. За минуту это незаметно, но сервер работает неделями: память понемногу заканчивается, приложение начинает тормозить, система уходит в подкачку на диск, а затем процесс аварийно завершается. Типичная картина — сервис, который «приходится перезапускать раз в несколько дней».

Как распознать

  • График потребления памяти процессом растет «лесенкой» и не возвращается вниз даже ночью, когда нагрузки нет.
  • Со временем растут паузы сборки мусора: сборщику приходится обходить все больше живых объектов.
  • Процесс падает с ошибкой нехватки памяти или его завершает ядро ОС — обычно примерно через одинаковое время работы.

Поэтому потребление памяти каждого сервиса выводят на графики мониторинга, например в Prometheus и Grafana, и настраивают оповещения о постоянном росте.

Откуда берутся утечки

В языках без автоматического управления памятью, C и C++, утечка — это забытое освобождение выделенной памяти. В языках со сборкой мусора — Java, C#, Python, JavaScript — сборщик освобождает только то, на что больше никто не ссылается. Утечка возникает, когда ссылка остается по ошибке:

  • Кэш без ограничений. Результаты складываются в словарь «на будущее» и никогда не удаляются; подробнее о правильном кэшировании — в термине «Кэширование».
  • Глобальные коллекции, в которые только добавляют.
  • Забытые подписки на события.
  • Незакрытые ресурсы — соединения, файлы, потоки.
// JavaScript
// Каждый раз при открытии окна добавляется новый обработчик,
// и вместе с ним в памяти навсегда остается все окно
function openDialog(dialog) {
  window.addEventListener("resize", () => dialog.reposition());
}

Обработчик ссылается на окно, а глобальный объект window — на обработчик. Окно закрыли, но память не освободится. Исправление — удалять обработчик при закрытии.

Как находят утечки

  • Снимки памяти в разные моменты и их сравнение: какие объекты копятся и кто на них ссылается. Для Java используют дампы памяти и анализаторы, для JavaScript — инструменты разработчика в браузере, для Python — модуль tracemalloc.
  • Инструменты для C и C++ — Valgrind и встроенные в компиляторы проверки памяти.
  • Нагрузочные тесты на долгий срок: сервис гоняют сутками и смотрят, возвращается ли память к исходному уровню.

Перезапуск — не лечение

Регулярный перезапуск сервиса маскирует утечку, но не устраняет ее: при росте нагрузки память кончается быстрее, и сбой происходит в самый неудобный момент. Утечку находят и исправляют в коде, а мониторинг оперативной памяти помогает убедиться, что исправление сработало.