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

Расскажи про две версии сборщика мусора (Garbage collector) в Python

1.8 Middle🔥 161 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Сборщик мусора в Python: две версии и эволюция

Сборщик мусора (Garbage Collector) в Python прошёл значительную эволюцию. Разберём основные версии, их работу и различия.

Reference Counting (Подсчёт ссылок)

Это основной механизм управления памятью в Python, который работает всегда:

import sys

# Создание объекта
x = []
print(sys.getrefcount(x))  # 2 (сама ссылка + параметр getrefcount)

y = x  # Добавляем ещё одну ссылку
print(sys.getrefcount(x))  # 3

del y  # Удаляем ссылку
print(sys.getrefcount(x))  # 2

# Когда счётчик ссылок = 0, объект удаляется немедленно
def create_list():
    temp = [1, 2, 3]  # счётчик = 1
    # Когда функция заканчивается, счётчик = 0
    # Объект удаляется из памяти СРАЗУ

create_list()

Плюсы reference counting:

  • Детерминированное удаление (освобождение памяти происходит сразу)
  • Предсказуемое поведение
  • Низкие overhead для простых случаев
  • Объекты удаляются в правильное время (финализаторы вызываются сразу)
class Resource:
    def __init__(self, name):
        self.name = name
    
    def __del__(self):
        print(f"Resource {self.name} is being freed")

r = Resource("db_connection")
print("Before delete")
del r  # Выведет "Resource db_connection is being freed" СРАЗУ
print("After delete")

Минусы reference counting:

  • Не справляется с циклическими ссылками
  • Overhead на каждое изменение ссылки

Циклические ссылки - проблема reference counting

import sys

# Проблема: циклическая ссылка
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

node1 = Node(1)
node2 = Node(2)

node1.next = node2
node2.next = node1  # Циклическая ссылка!

print(f"node1 refcount: {sys.getrefcount(node1)}")
print(f"node2 refcount: {sys.getrefcount(node2)}")

del node1  # Счётчик не = 0 из-за циклической ссылки!
del node2  # Объекты остаются в памяти!

# УТЕЧКА ПАМЯТИ

Mark-and-Sweep GC (Пометка и метание)

Периодический сборщик мусора, работающий отдельно:

import gc

# Получение информации о сборщике мусора
print(gc.get_count())  # (700, 10, 10) - счётчики для трёх поколений

# Объекты с циклическими ссылками
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1

del node1
del node2

# Объекты ещё в памяти, но mark-and-sweep найдёт их
gc.collect()  # Явно запускаем сборщик
print("Garbage collected")

# Отключение автоматического сборщика
gc.disable()

# Работаем без сборщика мусора
for i in range(1000000):
    x = list(range(1000))

# Собираем мусор явно
gc.collect()
gc.enable()

Поколения (Generational GC)

Mark-and-Sweep работает поколениями для оптимизации:

import gc

# Поколение 0: молодые объекты (сбор часто)
# Поколение 1: средние объекты (сбор редче)
# Поколение 2: старые объекты (сбор ещё реже)

print(gc.get_threshold())  # (700, 10, 10)
# 700: сборка поколения 0 после создания 700 объектов
# 10: сборка поколения 1 после 10 сборок поколения 0
# 10: сборка поколения 2 после 10 сборок поколения 1

# Изменение параметров
gc.set_threshold(1000, 15, 15)

# Явная сборка конкретного поколения
gc.collect(generation=0)  # Собрать только молодые объекты
gc.collect(generation=1)  # Собрать среднего возраста
gc.collect(generation=2)  # Собрать все (самое долгое)

Эволюция: Python 3.11+ с Per-Interpreter GIL

Novella версия даёт больше контроля:

# Python 3.11+ - больше контроля над GC
import gc

# Включение/отключение сборщика
if gc.isenabled():
    print("GC enabled")
else:
    print("GC disabled")

# Объекты, отслеживаемые сборщиком
print(gc.get_tracked_objects())  # Объекты с циклическими ссылками

# Отладка утечек памяти
gc.set_debug(gc.DEBUG_SAVEALL)  # Сохранять удаляемые объекты

# После сборки мусора
gc.collect()

# Получение объектов, которые были удалены
unreachable = gc.garbage
for obj in unreachable:
    print(f"Unreachable: {type(obj).__name__}")

gc.set_debug(0)  # Отключить режим отладки

Практические примеры

Пример 1: Обнаружение циклических ссылок

import gc

class LinkedList:
    def __init__(self, value):
        self.value = value
        self.next = None

# Создание циклической ссылки
head = LinkedList(1)
current = head

for i in range(2, 5):
    current.next = LinkedList(i)
    current = current.next

# Замыкаем цикл
current.next = head

# Удаляем все ссылки
del head
del current

# Reference counting найдёт только часть
# Остальное найдёт GC при сборке
gc.collect()
print("Cyclic references cleaned up")

Пример 2: Оптимизация production кода

import gc
import time

def process_large_dataset():
    # Отключаем автоматический GC для быстрой обработки
    gc_was_enabled = gc.isenabled()
    gc.disable()
    
    try:
        # Интенсивная обработка
        results = []
        for i in range(1000000):
            data = process_item(i)
            if data:
                results.append(data)
            
            # Периодически собираем мусор вручную
            if i % 10000 == 0:
                gc.collect(generation=0)  # Собрать только молодые
        
        return results
    finally:
        # Восстанавливаем исходное состояние
        if gc_was_enabled:
            gc.enable()
            gc.collect()  # Финальная очистка

def process_item(i):
    return i * 2 if i % 2 == 0 else None

Пример 3: Отследение утечек памяти

import gc
import sys
from typing import List

class MemoryLeakDetector:
    @staticmethod
    def find_cycles():
        """Найти циклические ссылки"""
        gc.set_debug(gc.DEBUG_SAVEALL)
        before = len(gc.get_objects())
        gc.collect()
        after = len(gc.get_objects())
        
        print(f"Объектов до сборки: {before}")
        print(f"Объектов после сборки: {after}")
        print(f"Удалено: {before - after}")
        
        # Анализ типов удаляемых объектов
        if gc.garbage:
            print("\nУдаляемые объекты:")
            type_counts = {}
            for obj in gc.garbage:
                obj_type = type(obj).__name__
                type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
            
            for obj_type, count in sorted(type_counts.items(), key=lambda x: x[1], reverse=True):
                print(f"  {obj_type}: {count}")
        
        gc.set_debug(0)
        gc.garbage.clear()
    
    @staticmethod
    def get_memory_info():
        """Информация об использовании памяти"""
        stats = gc.get_stats()
        for gen, stat in enumerate(stats):
            print(f"Generation {gen}:")
            print(f"  Collections: {stat.get('collections', 0)}")
            print(f"  Collected: {stat.get('collected', 0)}")
            print(f"  Uncollectable: {stat.get('uncollectable', 0)}")

detector = MemoryLeakDetector()
detector.find_cycles()
detector.get_memory_info()

Сравнение двух подходов

ХарактеристикаReference CountingMark-and-Sweep GC
Когда работаетПостоянноПериодически
Скорость обнаружения утечекМоментально (если нет циклов)Отложенная
Циклические ссылкиНе обрабатываетОбрабатывает
Overhead памятиНа каждый объектПериодическая сборка
ПредсказуемостьВысокаяНизкая (может резко замедлить)
GILНеобходим для потокобезопасностиМожет быть оптимизирован

Best Practices

# 1. Избегай циклических ссылок
class BadDesign:
    def __init__(self):
        self.circular = self  # Плохо!

# 2. Используй слабые ссылки для обратных ссылок
import weakref

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

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

# 3. Явно удаляй большие объекты
big_list = [0] * 1000000
data = process(big_list)
del big_list  # Освобождаем память сразу

# 4. Мониторь использование памяти в production
import tracemalloc

tracemalloc.start()
# ... код ...
current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 1024 / 1024:.2f} MB")
print(f"Peak: {peak / 1024 / 1024:.2f} MB")
tracemalloc.stop()

В Python используются оба механизма: Reference Counting как основной для простых случаев, и Mark-and-Sweep как дополнение для циклических ссылок и оптимизации. Это гибридный подход, обеспечивающий хорошую производительность и надежность.

Расскажи про две версии сборщика мусора (Garbage collector) в Python | PrepBro