Память и сборка мусора
Подсчёт ссылок, сборщик мусора, copy и deepcopy.
7 вопросов
JuniorТеорияОчень частоКак подсчёт ссылок освобождает память?
Как подсчёт ссылок освобождает память?
Каждый объект хранит счётчик ссылок; каждая новая привязка увеличивает его, удаление или переприсваивание уменьшает. Когда счётчик достигает нуля, объект освобождается сразу — основной механизм CPython.
Типичные ошибки
- ✗Думать, что память освобождается только периодическим сборщиком мусора, а не подсчётом ссылок
- ✗Забывать, что
sys.getrefcount()показывает на одну ссылку больше из-за своего аргумента - ✗Считать, что подсчёт ссылок сам может освобождать циклические ссылки
Уточняющие вопросы
- →Почему один лишь подсчёт ссылок не может освободить циклическую ссылку?
- →Какую цену потокобезопасности накладывает подсчёт ссылок через GIL?
JuniorТеорияЧастоВ чём разница между copy() и deepcopy()?
В чём разница между copy() и deepcopy()?
copy.copy() создаёт новый внешний объект, но копирует ссылки на вложенные, поэтому мутация вложенного элемента видна в обоих. copy.deepcopy() рекурсивно копирует всё ради полной независимости.
Типичные ошибки
- ✗Считать, что
copy()глубоко копирует вложенные списки или словари и мутации остаются изолированными - ✗Забывать, что присваивание
b = aничего не копирует, а лишь связывает другое имя - ✗Считать
deepcopy()всегда безопасным — он может рекурсировать бесконечно без обработки циклов
Уточняющие вопросы
- →Как
deepcopy()обрабатывает объект, содержащий циклическую ссылку? - →Как
__copy__и__deepcopy__позволяют настроить копирование вашего класса?
MiddleТеорияИногдаЗачем CPython нужен сборщик мусора циклов поверх подсчёта ссылок?
Зачем CPython нужен сборщик мусора циклов поверх подсчёта ссылок?
Подсчёт ссылок не освобождает циклические ссылки — объекты, ссылающиеся друг на друга, держат счётчики друг друга выше нуля даже недостижимыми. Опциональный сборщик циклов (gc) освобождает такие циклы.
Типичные ошибки
- ✗Считать, что сборщик циклов заменяет подсчёт ссылок, а не дополняет его
- ✗Полагать, что один подсчёт ссылок корректно обрабатывает циклы
- ✗Забывать, что
gcотслеживает лишь контейнерные объекты, способные образовать циклы
Уточняющие вопросы
- →Как сборщик циклов отличает недостижимый цикл от ещё используемого?
- →Как
__del__у объектов в цикле взаимодействует со сборщиком циклов?
MiddleТеорияИногдаКак поколенческий сборщик циклов организует объекты?
Как поколенческий сборщик циклов организует объекты?
Он делит отслеживаемые контейнеры на 3 поколения. Новые попадают в поколение 0, сканируемое чаще всего; выжившие повышаются в старшие, сканируемые реже (пороги 700, 10, 10), ведь молодые умирают быстро.
Типичные ошибки
- ✗Думать, что старшие поколения сканируются чаще, а не реже
- ✗Считать, что атомарные неизменяемые вроде
intотслеживаются сборщиком циклов - ✗Путать порог поколения 0 (выделения минус освобождения) с интервалом времени
Уточняющие вопросы
- →Что именно считает порог 700 поколения 0 перед запуском?
- →Как
gc.get_threshold()иgc.set_threshold()позволяют настроить сборку?
MiddleДебаггингИногдаПочему изменение поверхностной копии меняет оригинал?
Почему изменение поверхностной копии меняет оригинал?
copy.copy поверхностна: внешний список новый, но внутренние списки — общие ссылки, поэтому изменение shallow[0] меняет и original → [[1, 2, 99], [3, 4]]. Решение: использовать copy.deepcopy(original) для полностью независимых вложенных данных.
Типичные ошибки
- ✗Думать, что
copy.copyглубоко копирует вложенные структуры - ✗Считать, что срез
[:]делает внутренние списки независимыми - ✗Не использовать
copy.deepcopyдля вложенных данных
Уточняющие вопросы
- →Когда поверхностная копия действительно предпочтительнее глубокой?
- →Как
copy.deepcopyобрабатывает структуру со ссылочным циклом?
SeniorТеорияРедкоМожно ли отключить подсчёт ссылок или сборщик циклов?
Можно ли отключить подсчёт ссылок или сборщик циклов?
Подсчёт ссылок фундаментален для CPython и не отключается. Сборщик циклов опционален: gc.disable() выключает его, а gc.collect() запускает вручную — избегая всплесков пауз ценой утечки циклов.
Типичные ошибки
- ✗Думать, что
gc.disable()останавливает всё освобождение, а не только сборщик циклов - ✗Считать, что подсчёт ссылок можно отключить ради производительности
- ✗Забывать, что
gc.disable()рискует утечкой циклов доgc.enable()илиgc.collect()
Уточняющие вопросы
- →Когда отключение
gcна время пакетной загрузки реально окупается? - →Что возвращает
gc.collect()и чемgc.freeze()помогает при fork?
SeniorТеорияРедкоПочему долгоживущий процесс может не возвращать освобождённую память ОС?
Почему долгоживущий процесс может не возвращать освобождённую память ОС?
CPython управляет малыми объектами (<512 Б) через свой аллокатор pymalloc, переиспользуя освобождённую память в пулах, а не возвращая ОС. Арена отдаётся ОС лишь когда пуста — рост не всегда утечка.
Типичные ошибки
- ✗Приравнивать неуменьшающийся RSS к утечке памяти
- ✗Считать, что каждый
delсразу уменьшает размер процесса в памяти - ✗Думать, что сборщик мусора, а не pymalloc, управляет возвратом памяти ОС
Уточняющие вопросы
- →Как
arenasиpoolsв pymalloc отображаются на страницы памяти ОС? - →Почему крупные выделения свыше 512 байт идут напрямую в
malloc?