← Назад к вопросам
Как работает очистка поколений в сборщике мусора (Garbage collector)?
3.0 Senior🔥 151 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает очистка поколений в сборщике мусора (Garbage collector)
Python использует гибридный подход: подсчёт ссылок (reference counting) + сборка мусора по поколениям (generational GC). Это один из самых сложных механизмов в Python.
1. Подсчёт ссылок (Reference Counting)
Основной механизм очистки памяти в Python:
import sys
# Каждый объект имеет счётчик ссылок
a = []
print(sys.getrefcount(a)) # 2 (одна ссылка a + одна для аргумента getrefcount)
b = a # +1 ссылка
print(sys.getrefcount(a)) # 3
c = a # +1 ссылка
print(sys.getrefcount(a)) # 4
del b # -1 ссылка
print(sys.getrefcount(a)) # 3
del c # -1 ссылка
print(sys.getrefcount(a)) # 2
del a # -1 ссылка, счётчик = 1, объект удаляется
# Объект удалён из памяти
Проблема: циклические ссылки не удаляются:
class Node:
def __init__(self):
self.ref = self # циклическая ссылка
node = Node()
print(sys.getrefcount(node)) # 2
del node # Счётчик = 1, но объект не удаляется!
# Объект остаётся в памяти
2. Поколения в сборщике мусора
Python использует 3 поколения для обнаружения циклических ссылок:
import gc
# Проверь текущее состояние GC
print(gc.get_count()) # (objects_gen0, objects_gen1, objects_gen2)
# Например: (500, 10, 5)
# Пороги для автоматической сборки
print(gc.get_threshold()) # (700, 10, 10)
# Gen0 собирается каждые 700 создаваемых объектов
# Gen1 собирается после 10 сборок Gen0
# Gen2 собирается после 10 сборок Gen1
# Изменение порогов
gc.set_threshold(1000, 15, 15)
Как это работает:
- Поколение 0: новые объекты, собирается часто
- Поколение 1: объекты, пережившие 1 сборку Gen0
- Поколение 2: объекты, пережившие 1 сборку Gen1 (долгоживущие)
# Жизненный цикл объекта
obj = [] # Добавляется в Gen0
# Поколение 0 переполняется -> сборка мусора
# obj выживает -> переходит в Gen1
# Поколение 1 переполняется -> сборка мусора
# obj выживает -> переходит в Gen2
# Gen2 собирается редко (долгоживущие объекты)
3. Алгоритм Mark-and-Sweep для циклических ссылок
# Пример циклической ссылки
class Node:
def __init__(self, value):
self.value = value
self.next = None
# Создаём цикл
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # Циклическая ссылка
# Счётчик ссылок:
# node1: 2 ссылки (переменная + node2.next)
# node2: 2 ссылки (переменная + node1.next)
# Если удалить переменные:
del node1, node2
# Счётчики обе = 1, но объекты не удаляются!
# Это задача для сборщика мусора
# Сборщик мусора:
# 1. MARK фаза: найти все достижимые объекты из корней
# 2. SWEEP фаза: удалить недостижимые объекты
import gc
gc.collect() # Принудительный сбор мусора
# Объекты node1 и node2 удаляются здесь
4. Управление сборщиком мусора
import gc
import sys
# Отключение автоматического сбора мусора
gc.disable()
print(gc.isenabled()) # False
# Твой код
for i in range(1000000):
x = []
y = x
del x, y
# Память не очищается!
# Принудительная сборка мусора
gc.collect()
print(gc.isenabled()) # False
# Включение автоматического сбора мусора
gc.enable()
print(gc.isenabled()) # True
5. Отслеживание сборки мусора
import gc
# Включение отладки
gc.set_debug(gc.DEBUG_SAVEALL)
# Сборка мусора
gc.collect()
# Посмотри, что было собрано
print(f"Garbage collected: {len(gc.garbage)} objects")
for obj in gc.garbage[:10]:
print(type(obj))
# Отключение отладки
gc.set_debug(0)
6. Поколения в действии
import gc
class Tracker:
def __init__(self, gen):
self.generation = gen
def __del__(self):
print(f"Object from generation {self.generation} deleted")
# Отслеживание
gc.collect()
print(f"GC threshold: {gc.get_threshold()}")
# Создание объектов в Gen0
objs_gen0 = [Tracker(0) for _ in range(100)]
print(f"Count: {gc.get_count()}") # Gen0 растёт
# Достижение порога Gen0
while gc.get_count()[0] < 700:
x = []
print("Gen0 полна, сборка мусора...")
gc.collect() # Принудительная сборка
# Объекты перейдут в Gen1
print(f"Count after collection: {gc.get_count()}")
7. Оптимизация производительности
# Проблема: частая сборка мусора замораживает приложение
import time
import gc
# Профилирование сборки мусора
start = time.time()
for i in range(1000000):
x = []
end = time.time()
print(f"With GC: {end - start:.2f}s")
# Оптимизация: отключение GC на время критичного кода
gc.disable()
start = time.time()
for i in range(1000000):
x = []
end = time.time()
print(f"Without GC: {end - start:.2f}s") # Намного быстрее
# Сборка после критичного кода
gc.collect()
gc.enable()
# Увеличение порогов для больших приложений
gc.set_threshold(10000, 15, 15)
8. Утечки памяти и отладка
import gc
# Найти объекты, на которые ничто не ссылается
def find_cycles():
gc.collect()
unreachable = gc.garbage[:]
return unreachable
# Посмотри, что не собирается
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect()
if gc.garbage:
print(f"Found {len(gc.garbage)} unreachable objects")
for obj in gc.garbage[:5]:
print(f"Type: {type(obj)}, Size: {sys.getsizeof(obj)}")
# Отключение отладки
gc.set_debug(0)
9. Проблема: del методы
class Resource:
def __init__(self):
self.data = b"X" * 1000000
def __del__(self):
print("Cleaning up...")
# __del__ вызывается при удалении объекта
# Проблема: циклические ссылки с __del__
class Node:
def __init__(self):
self.next = None
self.resource = Resource()
def __del__(self):
print("Node deleted")
node1 = Node()
node2 = Node()
node1.next = node2
node2.next = node1 # Циклическая ссылка
del node1, node2
# Сборщик мусора НЕ может удалить эти объекты!
# Потому что не знает, в каком порядке вызывать __del__
# Решение: использовать weakref
import weakref
class SafeNode:
def __init__(self):
self._next = None
@property
def next(self):
return self._next() if self._next else None
@next.setter
def next(self, value):
self._next = weakref.ref(value) if value else None
10. Лучшие практики
- Избегай циклических ссылок — используй weakref если необходимо
- Не полагайся на del — используй контекстные менеджеры (with)
- Отключай GC для критичного кода — если нужна максимальная производительность
- Профилируй утечки памяти — tracemalloc и gc.garbage помогут
- Используй контекстные менеджеры — обеспечивают детерминированную очистку:
# Хорошо
with open('file.txt') as f:
data = f.read()
# Файл гарантированно закроется, не зависит от GC
# Плохо
f = open('file.txt')
data = f.read()
# Файл может остаться открытым, пока не запустится GC
Помни: поколения оптимизированы для типичного поведения объектов — новые объекты часто удаляются быстро, долгоживущие объекты редко участвуют в сборке.