Утечка памяти (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 и встроенные в компиляторы проверки памяти.
- Нагрузочные тесты на долгий срок: сервис гоняют сутками и смотрят, возвращается ли память к исходному уровню.
Перезапуск — не лечение
Регулярный перезапуск сервиса маскирует утечку, но не устраняет ее: при росте нагрузки память кончается быстрее, и сбой происходит в самый неудобный момент. Утечку находят и исправляют в коде, а мониторинг оперативной памяти помогает убедиться, что исправление сработало.