Как работает Garbage Collector и reference counting в Python?

В CPython основной механизм управления памятью — reference counting: у каждого объекта есть счётчик ссылок, и как только он падает до нуля, объект сразу освобождается. Дополнительно поверх этого работает генерационный garbage collector, который периодически ищет и удаляет объекты с циклическими ссылками, которые reference counting сам по себе освободить не может.

Подробный ответ

В 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 решает проблему циклических ссылок.

Оцени свой прогресс

Честно оцени своё понимание этого вопроса, чтобы мы могли построить твой учебный трек максимально эффективно.
Читать в блоге