Перейти к содержанию

Управление памятью и сборка мусора

Начало урока

Управление памятью и сборка мусора

Как CPython выделяет, отслеживает и освобождает объекты во время выполнения

Главная идея: CPython освобождает память с помощью двух взаимодействующих механизмов. Подсчёт ссылок освобождает объект в тот же миг, как на него никто не ссылается; циклический сборщик мусора существует только для единственного случая, с которым подсчёт ссылок не справляется — групп объектов, которые ссылаются друг на друга, но недостижимы из вашего кода. Понимание того, где начинается и где заканчивается каждый механизм, и позволяет рассуждать об утечках, порядке финализаторов и профиле памяти долго живущего сервиса.
К концу вы сможете:
  • объяснить, как подсчёт ссылок определяет момент освобождения объекта и почему sys.getrefcount всегда показывает на единицу больше;
  • описать, как циклические ссылки обходят чистый подсчёт ссылок и как поколенческий сборщик их освобождает;
  • использовать weakref, чтобы наблюдать за объектом или кэшировать его, не удерживая его в живых;
  • избегать классических ошибок __del__ (воскрешение, неопределённый порядок в циклах, завершение работы);
  • диагностировать неожиданный рост памяти с помощью tracemalloc, gc и sys.getsizeof.

Эта тема предполагает, что вы уже знаете объектную модель Python — идентичность, изменяемость и семантику копирования из предыдущей темы. Здесь мы спускаемся на уровень глубже, к машинерии времени выполнения, которая удерживает эти объекты в живых.

1. Подсчёт ссылок: основной механизм

Каждый создаваемый CPython объект несёт небольшое целочисленное поле ob_refcnt, которое считает, сколько ссылок сейчас на него указывает. Привязка имени, добавление в список, передача аргумента — каждое из этих действий прибавляет единицу к счётчику; удаление имени, его переприсваивание или выход из области видимости вычитают единицу. Правило точное и мгновенное: как только счётчик достигает нуля, объект освобождается, запускается его __del__ (если он есть), и освобождаются его собственные ссылки — что может каскадом освободить ещё больше объектов.

import sys

a = [10, 20, 30]     # у объекта-списка теперь одна ссылка: 'a'
b = a                # вторая ссылка на ТОТ ЖЕ объект
print(sys.getrefcount(a))   # 3  (a, b и собственный аргумент getrefcount)

del b                # одна ссылка исчезла
print(sys.getrefcount(a))   # 2  (a и аргумент)

del a                # последняя реальная ссылка исчезла -> счётчик -> 0 -> освобождён сейчас

Именно поэтому CPython освобождает большую часть памяти детерминированно и быстро, без паузы «остановки мира»: освобождение вплетено в обычный учёт ссылок. Поэтому же файл, закрытый менеджером контекста, освобождается сразу, как только блок with завершается и исчезает его последняя ссылка.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя