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

Зачем нужен сборщик мусора (Garbage Collector)?

1.3 Junior🔥 131 комментариев
#Python Core

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

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

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

Сборщик мусора (Garbage Collector) в Python

Сборщик мусора — это механизм, который автоматически освобождает память, занятую объектами, которые больше не используются. Без него программа постепенно исчерпала бы всю доступную память.

Зачем он нужен: проблема управления памятью

Вручную (как в C):

int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);  // НЕЛЬЗЯ ЗАБЫТЬ!

Если забыть вызвать free(), произойдёт утечка памяти. Если вызвать free() дважды — крах программы.

Python (автоматически):

obj = SomeObject()
obj.do_something()
# Когда obj больше не используется, память автоматически освобождается

Как работает GC в Python: Reference Counting

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

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

Каждый объект в Python имеет счётчик ссылок — количество переменных/контейнеров, указывающих на этот объект.

import sys

obj = []  # Счётчик = 1 (переменная obj)
ref1 = obj  # Счётчик = 2 (переменная ref1)
ref2 = obj  # Счётчик = 3 (переменная ref2)

print(sys.getrefcount(obj))  # 4 (+ скрытые ссылки в самой функции)

del ref1  # Счётчик = 3
del ref2  # Счётчик = 2
del obj   # Счётчик = 1, объект удаляется из памяти

В момент, когда счётчик ссылок становится 0, объект сразу же удаляется.

Преимущество: Быстро, предсказуемо, работает хорошо для большинства случаев.

Недостаток: Не может обработать циклические ссылки.

Проблема циклических ссылок

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

# Память НЕ освобождена! node1 -> node2 -> node1
# Счётчик ссылок node1 = 1 (node2 ссылается на него)
# Счётчик ссылок node2 = 1 (node1 ссылается на него)
# Оба > 0, поэтому они не удаляются

Это решается сборщиком мусора второго уровня.

Уровень 2: Сборщик мусора (Garbage Collector)

Для обработки циклических ссылок Python имеет GC модуль:

import gc

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

# Сборщик мусора периодически запускается и обнаруживает циклы
gc.collect()  # Принудительный запуск
# Теперь память освобождена

Сборщик мусора:

  1. Находит циклы объектов
  2. Проверяет, достижимы ли они из корневых объектов
  3. Если нет — удаляет весь цикл целиком

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

Python использует трёхпоколенческую систему GC для оптимизации:

import gc

# Получить информацию о сборщике мусора
print(gc.get_stats())
# generation 0: ~1000 новых объектов -> полная сборка
# generation 1: ~10000 объектов -> сборка реже
# generation 2: ~100000 объектов -> сборка очень редко

# Настроить пороги сборки
gc.set_threshold(700, 10, 10)  # generation 0, 1, 2

Логика: Новые объекты быстро становятся "мусором", так что generation 0 проверяется часто. Старые объекты обычно живут долго, так что их проверяют редко.

generation 0 (молодые):        X X X X X X X  (проверка часто)
                                     ↓ дожили
generation 1 (средние):         X X X  (проверка иногда)
                                     ↓ дожили
generation 2 (старые):          X X (проверка редко)

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

1. Циклические ссылки в контейнерах:

# ПЛОХО
data = []
data.append(data)  # Циклическая ссылка!
# Будет удалено только через GC

# ХОРОШО
data = []
data.append(None)  # Или другой безопасный объект

2. Кеши без лимита размера:

# ПЛОХО
cache = {}  # Обычный dict растёт бесконечно

def process(key):
    if key not in cache:
        cache[key] = expensive_computation(key)
    return cache[key]

for i in range(1000000):
    process(i)  # Кеш займёт всю память

# ХОРОШО
from functools import lru_cache

@lru_cache(maxsize=10000)  # Максимум 10000 элементов
def process(key):
    return expensive_computation(key)

3. Слушатели событий без отписки:

class EventEmitter:
    def __init__(self):
        self.listeners = []
    
    def on(self, listener):
        self.listeners.append(listener)
    
    def emit(self):
        for listener in self.listeners:
            listener()

emitter = EventEmitter()

class Observer:
    pass

observer = Observer()
emitter.on(observer.handle)  # Подписываемся

del observer  # Observer всё ещё в памяти! На него ссылается emitter
# НУЖНО: emitter.listeners.remove(observer.handle)

Управление GC в коде

import gc

# 1. Отключить автоматический GC (для performance-critical кода)
gc.disable()
try:
    # Критичная для производительности операция
    critical_operation()
finally:
    gc.collect()  # Явный сборка
    gc.enable()

# 2. Получить статистику
print(gc.get_count())  # (count0, count1, count2)
print(gc.get_stats())  # Подробная статистика

# 3. Найти объекты, участвующие в циклах
gc.collect()  # Сначала собрать мусор
garbages = gc.garbage  # Объекты, которые невозможно удалить
print(len(garbages))

# 4. Отследить утечки (debug mode)
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect()
for obj in gc.garbage:
    print(f"Мусор: {type(obj)}")

Когда отключать GC

Для очень критичного по производительности кода можно отключить GC:

import gc
import time

def benchmark_with_gc():
    gc.enable()
    start = time.time()
    for i in range(1000000):
        _ = []
    return time.time() - start

def benchmark_without_gc():
    gc.disable()
    start = time.time()
    for i in range(1000000):
        _ = []
    gc.collect()  # Одна сборка в конце
    return time.time() - start

print(f"С GC: {benchmark_with_gc():.3f}s")
print(f"Без GC: {benchmark_without_gc():.3f}s")
# Без GC может быть на 5-10% быстрее

Сравнение с другими языками

ЯзыкУправление памятьюПерспективы
C/C++Вручную (malloc/free)Быстро, но опасно
JavaСборщик мусораПростой, но может быть медленным
PythonReference Counting + GCХорошо для большинства случаев
RustLifetime systemОчень быстро, но сложно

Лучшие практики

  1. Избегай циклических ссылок:
# ПЛОХО
class Parent:
    def __init__(self):
        self.child = Child()
        self.child.parent = self  # Циклическая ссылка

# ХОРОШО
import weakref

class Parent:
    def __init__(self):
        self.child = Child()
        self.child.parent = weakref.ref(self)  # Слабая ссылка
  1. Используй контекст-менеджеры для очистки ресурсов:
with open("file.txt") as f:
    data = f.read()  # Автоматически закроется
  1. Ограничивай размер кешей:
from cachetools import LRUCache

cache = LRUCache(maxsize=1000)
  1. Явно отписывайся от событий:
emitter.on(handler)
# ...
emitter.remove_listener(handler)
  1. Профилируй утечки памяти:
import memory_profiler

@profile
def my_function():
    large_list = [i for i in range(1000000)]
    return sum(large_list)

# Запуск: python -m memory_profiler script.py

Выводы

Сборщик мусора в Python — это критически важный компонент, который:

  1. Автоматически освобождает неиспользуемую память
  2. Обрабатывает сложные циклические ссылки
  3. Позволяет разработчику не беспокоиться о ручном управлении памятью

Однако для production-приложений нужно понимать, как он работает, чтобы избежать утечек памяти и оптимизировать производительность.