Предполагается, что вы уже умеете писать простые программы на Python и знаете базовый синтаксис (if, for, функции, списки). Здесь мы разбираем не как пользоваться языком, а почему он работает именно так.
В предыдущей статье мы познакомились с подсчётом ссылок.
Идея оказалась удивительно простой.
Пока на объект существует хотя бы одна ссылка, объект продолжает жить.
Когда последняя ссылка исчезает, Python понимает, что объект больше никому не нужен, и освобождает память.
Кажется, что этого механизма вполне достаточно.
Но давайте рассмотрим небольшой пример.
a = []
b = []
a.append(b)
b.append(a)
На первый взгляд здесь нет ничего необычного.
Мы создали два списка.
Но теперь каждый из них хранит ссылку на другой.
Схематично это выглядит так:
┌─────┐ ┌─────┐
│ a │ ─────► │ [] │
└─────┘ └──┬──┘
│
▼
┌────────┐
│ [] │
└────┬───┘
▲
│
└──────────────
Получился небольшой замкнутый круг.
Теперь удалим оба имени.
del a
del b
Что произойдёт?
На первый взгляд кажется, что оба списка должны исчезнуть.
Ведь имена a и b больше не существуют.
Однако ситуация оказывается немного сложнее.
Да, программа действительно потеряла возможность добраться до этих объектов.
Но сами объекты продолжают хранить ссылки друг на друга.
Первый список знает о втором.
Второй знает о первом.
Получается, что счётчики ссылок никогда не станут равны нулю.
Каждый объект продолжает ссылаться на своего соседа.
Возникает очень странная ситуация.
Для программы эти объекты уже недоступны.
Никаким способом получить их обратно нельзя.
Но освободить память подсчёт ссылок тоже не может.
Если бы Python использовал только этот механизм, такая память оставалась бы занятой до завершения программы.
К счастью, этого не происходит.
Именно здесь на сцену выходит ещё один механизм.
Он называется сборщик мусора (Garbage Collector).
Его задача - находить объекты, которые больше невозможно использовать, даже если их счётчики ссылок не равны нулю.
Можно представить себе, что время от времени Python задаёт вопрос:
“Есть ли объекты, до которых уже невозможно добраться из работающей программы?”
Если такие объекты находятся, они удаляются вместе со всеми внутренними ссылками.
Именно поэтому циклические ссылки не приводят к постоянной утечке памяти.
Важно понимать одну вещь.
Garbage Collector не заменяет подсчёт ссылок.
Он лишь помогает ему справляться с ситуациями, которые невозможно решить простым подсчётом.
Можно сказать, что эти два механизма работают вместе.
Сначала подсчёт ссылок быстро освобождает большинство объектов.
А затем Garbage Collector время от времени проверяет, не осталось ли где-нибудь “забытых островков памяти”, до которых программа уже не может добраться.
На практике большинство объектов удаляются именно благодаря подсчёту ссылок.
Garbage Collector вмешивается значительно реже.
Поэтому в обычной программе вы можете вообще никогда не заметить его работу.
Но если бы его не существовало, любая программа, создающая циклические ссылки, постепенно расходовала бы всё больше памяти.
Если посмотреть на путь, который мы уже прошли, становится понятно, что никакой магии здесь нет.
Сначала мы узнали, что переменные - это всего лишь имена.
Потом увидели, что несколько имён могут ссылаться на один объект.
Разобрались, когда объект изменяется, а когда создаётся новый.
Поняли, что del удаляет имя, а не объект.
Узнали, что Python следит за количеством ссылок.
И наконец выяснили, почему одного только подсчёта ссылок недостаточно.
Все эти идеи складываются в единую картину того, как Python управляет памятью.
Теперь возникает следующий интересный вопрос.
Мы уже несколько раз говорили, что имя ссылается на объект.
Но где именно Python хранит все эти имена?
Почему одна и та же переменная иногда доступна внутри функции, а иногда нет?
И как Python понимает, какую переменную использовать, если одинаковые имена встречаются в разных местах программы?
Об этом мы поговорим в следующей статье.