В CPython у каждого объекта есть внутреннее поле refcount — количество активных ссылок на этот объект. При создании объекта счётчик = 1. Присвоили объект новой переменной, положили в список, передали в функцию — счётчик увеличился. Переменная вышла из области видимости, удалили элемент из списка, присвоили другое значение — счётчик уменьшился.
Как только счётчик становится равным 0, объект моментально деаллоцируется, память освобождается сразу, без ожидания прохода GC. Это даёт предсказуемый момент освобождения (важно, например, для файловых дескрипторов), меньшую потребность в тяжелых «stop‑the‑world» проходах сборщика. Но у механизма подсчета ссылок есть фундаментальная проблема: циклические ссылки.
Проблема циклических ссылок и роль GC
Если два (или больше) объекта ссылаются друг на друга, их refcount никогда не станет нулём, даже если до них нельзя добраться из кода программы.
a = {}
b = {}
a["b"] = b
b["a"] = a
del a, b # внешних ссылок больше нет, но объекты держат друг другаОба словаря остаются в памяти: refcount > 0 из-за взаимных ссылок, хотя они уже не достижимы из программы. Чтобы такие объекты не «висели» в памяти бесконечно, в CPython есть дополнительный сборщик циклов — модуль gc и встроенный алгоритм - генерационный GC.
Генерационный Garbage Collector
GC в Python заточен именно под обнаружение циклических ссылок. Он работает поверх счетчика ссылок и работает по принципу поколений:
Все новые объекты попадают в поколение 0 (младшее).
Если объект пережил один проход GC, он «стареет» и переезжает в поколение 1.
Выжившие ещё дольше попадают в поколение 2 (старшее).
GC чаще всего сканирует младшее поколение (там много короткоживущих объектов), реже — старшие поколения, чтобы не тратить ресурсы на длинноживущие объекты. Триггером служат пороги (thresholds): когда разница «создано – уничтожено» объектов превышает порог для поколения 0, запускается сборка этого поколения; после нескольких сборок 0‑го поколения может запуститься сборка 1‑го, и так далее. Алгоритмически это вариант mark‑and‑sweep, оптимизированный под циклы: GC ищет группы объектов, достижимые только друг через друга, и удаляет их как мусор.
Ключевые тезисы, которые ожидают услышать на собеседовании
Python (CPython) использует комбинированный подход: reference counting — основной механизм освобождения памяти; generational garbage collector — дополнительный механизм для циклических ссылок. Reference counting даёт немедленное освобождение, но не справляется с циклическими ссылками. GC работает поколениями, чаще проверяя молодые объекты, реже — старые, чтобы уменьшить overhead (расход ресурсов).
Разработчик может посмотреть статистику и пороги через модуль gc, вызвать gc.collect() вручную, временно отключить GC (gc.disable()), если он даёт заметные паузы, и управлять сборкой вручную, например, в узких местах производительности.
Краткая формулировка для ответа
В CPython память автоматически управляется двумя механизмами: счетчиком ссылок и генерационным GC. Каждый объект хранит счётчик ссылок; как только он падает до нуля, объект сразу освобождается. Но при циклических ссылках refcount никогда не становится нулём, поэтому поверх этого работает сборщик мусора, который периодически сканирует объекты (в трёх поколениях) и удаляет циклические ссылки. Счетчик ссылок обеспечивает быстрое и предсказуемое освобождение памяти, GC решает проблему циклических ссылок.