Как искать утечки памяти?
Что проверяют
Знаете ли вы, что утечка в Python это почти всегда живая ссылка, которую никто не отпускает, и умеете ли найти её инструментами, а не догадками. Следом часто спрашивают, как отличить утечку от нормального роста памяти и почему процесс не отдаёт память системе после освобождения объектов.
Как отвечать
Опишите порядок работы:
- подтвердить рост: снять RSS процесса под стабильной нагрузкой. Если память растёт линейно и не выходит на плато, это похоже на утечку. Однократный рост до плато обычно кеш или фрагментация;
- воспроизвести локально: прогнать сценарий тысячи раз в цикле;
tracemalloc: входит в стандартную библиотеку. Делаетеtake_snapshot()до и после серии итераций, затемsnapshot.compare_to(old, "lineno")покажет строки кода, где выделения выросли сильнее всего;- кто держит объект:
objgraph.show_growth()показывает, какие типы размножаются,objgraph.show_backrefs()рисует цепочку ссылок до корня, в стандартной библиотеке естьgc.get_referrers(); memray: профилировщик, который видит и выделения в C-расширениях, гдеtracemallocслеп.
Затем назовите типичные причины: глобальный словарь или список, куда только добавляют; functools.lru_cache(maxsize=None) без ограничения, особенно на методе, где кеш держит self; подписчики и колбэки, которые забыли отписать; трейсбеки и замыкания, сохранённые в долгоживущих объектах; утечки внутри C-расширений.
Закончите профилактикой: ограниченные кеши, weakref.WeakValueDictionary для реестров, тест на прирост памяти после множества повторов операции.
Где спотыкаются
Считают циклические ссылки утечкой. Циклы собирает gc, и после PEP 442 это касается и объектов с __del__. Цикл становится проблемой, только если на него есть живая внешняя ссылка.
Путают утечку с тем, что процесс не отдаёт память системе. Аллокатор переиспользует освобождённые блоки, а арену возвращает системе только целиком пустой, поэтому из-за фрагментации RSS может не падать, хотя объектов уже нет.
Вызывают gc.collect() в надежде, что он «найдёт утечку». Сборка освобождает недостижимое, а утекший объект по определению достижим, его ищут через цепочку ссылок.