← Назад к вопросам

Когда удаляется объект в сборщике мусора (Garbage collector)?

2.0 Middle🔥 141 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда объект удаляется в сборщике мусора Python

В Python используется двухуровневая система управления памятью:

  1. Reference counting (подсчёт ссылок) — основной механизм
  2. Garbage collector (сборщик мусора) — для циклических ссылок

Уровень 1: Reference Counting (счётчик ссылок)

Это основной механизм удаления в Python:

import sys

obj = []  # Создан объект, счётчик = 1
print(sys.getrefcount(obj))  # 2 (сам obj + аргумент getrefcount)

ref1 = obj  # Счётчик = 2
print(sys.getrefcount(obj))  # 3

ref2 = obj  # Счётчик = 3
print(sys.getrefcount(obj))  # 4

del ref1  # Счётчик = 3
print(sys.getrefcount(obj))  # 3

del ref2  # Счётчик = 2
print(sys.getrefcount(obj))  # 2

del obj  # Счётчик = 0, объект УДАЛЁН СРАЗУ
# __del__ вызывается здесь

Объект удаляется СРАЗУ когда ref count = 0, не ждёт сборщика мусора.

Уровень 2: Garbage Collector (для циклических ссылок)

Когда объекты ссылаются друг на друга, ref counting не помогает:

import gc

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None
    
    def __del__(self):
        print(f"{self.name} удалён")

# Циклическая ссылка
a = Node("A")
b = Node("B")
a.next = b  # a -> b
b.next = a  # b -> a (цикл!)

print(f"Ref count A: {sys.getrefcount(a)}")  # > 1
print(f"Ref count B: {sys.getrefcount(b)}")  # > 1

del a  # А НЕ удаляется! Есть цикл
del b  # B НЕ удаляется! Есть цикл

print("\nОбъекты ещё в памяти")

# Сборщик мусора находит циклы
gc.collect()  # Теперь удаляет A и B

print("Мусор собран")
# Вывод:
# A удалён
# B удалён

Когда вызывается сборщик мусора

1. Автоматически по поколениям

import gc

# Просмотр поколений
print(gc.get_threshold())  # (700, 10, 10)

# Поколение 0: 700 объектов -> gc.collect()
# Поколение 1: 10 сборок поколения 0 -> gc.collect()
# Поколение 2: 10 сборок поколения 1 -> gc.collect()

# Счётчики текущего состояния
print(gc.get_count())  # (объектов в поколении 0, 1, 2)

2. Явно вызываем gc.collect()

import gc

# Принудительно собрать мусор
collected = gc.collect()
print(f"Собрано объектов: {collected}")

3. При отключении сборщика

import gc

# Выключить автоматическую сборку
gc.disable()

# ... код работает быстрее, но память растёт

# Периодически собираем вручную
gc.collect()

# Включить обратно
gc.enable()

Практический пример с del

import gc
import sys

class Resource:
    def __init__(self, name):
        self.name = name
        print(f"{self.name}: __init__")
    
    def __del__(self):
        print(f"{self.name}: __del__ вызван")

# Сценарий 1: Простое удаление (ref count = 0)
print("--- Сценарий 1: простое удаление ---")
res1 = Resource("res1")  # создан
del res1  # __del__ вызван СРАЗУ
# Вывод:
# res1: __init__
# res1: __del__ вызван

print("\n--- Сценарий 2: циклическая ссылка ---")
class CircularResource:
    def __init__(self, name):
        self.name = name
        self.self_ref = None
        print(f"{self.name}: __init__")
    
    def __del__(self):
        print(f"{self.name}: __del__ вызван")

res2 = CircularResource("res2")
res2.self_ref = res2  # цикл!

del res2  # __del__ НЕ вызывается сразу
print("res2 удаляется после gc.collect()")

gc.collect()  # Теперь удаляется
# Вывод:
# res2: __init__
# res2: удаляется после gc.collect()
# res2: __del__ вызван

Проверка объектов в памяти

import gc
import sys

print("--- Анализ памяти ---")

# Получить все объекты
all_objects = gc.get_objects()
print(f"Всего объектов в памяти: {len(all_objects)}")

# Объекты с циклическими ссылками
gc.collect()
garbages = gc.garbage  # Только если DEBUG флаг включен
print(f"Необработанный мусор: {len(garbages)}")

# Отслеживание утечек памяти
def find_circular_refs():
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    
    for obj in gc.garbage:
        print(type(obj), id(obj))
    
    gc.set_debug(0)
    gc.garbage.clear()

find_circular_refs()

Важные моменты

1. Context managers для гарантированной очистки

# Плохо: полагаться на __del__
file = open("data.txt")
data = file.read()
# __del__ может не вызваться долго

# Хорошо: гарантированная очистка
with open("data.txt") as file:
    data = file.read()
# __exit__ вызывается сразу

2. weakref для избежания циклов

import weakref

class Parent:
    def __init__(self, name):
        self.name = name
        self.children = []

class Child:
    def __init__(self, name, parent):
        self.name = name
        self.parent = weakref.ref(parent)  # слабая ссылка!

parent = Parent("parent")
child = Child("child", parent)

del parent  # Удаляется, нет цикла
del child

3. Отключение GC для оптимизации

import gc

gc.disable()  # Отключить сборщик

# Код где нет циклических ссылок
for i in range(1_000_000):
    obj = {"data": i}
    # obj удаляется по ref count

gc.enable()

Резюме

СитуацияКогда удаляетсяКак удаляется
Простой объект, ref count = 0СРАЗУReference counting
Циклические ссылкиПри gc.collect()Garbage collector
После delЕсли ref count = 0Reference counting
В контексте withПри выходе из блока__exit__
Неиспользуемые переменныеВ конце scopeReference counting

Золотое правило: В Python ref counting удаляет объекты почти сразу, а gc.collect() ловит циклические ссылки.