Как работает сборщик мусора (GC) в Python?python-23

Основные механизмы управления памятью

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

  1. Счетчик ссылок (Reference Counting) - базовый механизм
  2. Циклический сборщик мусора (Generational Garbage Collector) - для обнаружения циклических ссылок

1. Счетчик ссылок

Каждый объект в Python содержит счетчик ссылок, который увеличивается при создании новой ссылки и уменьшается при удалении ссылки.

import sys

a = []  # Создаем объект, счетчик = 1
b = a   # Счетчик увеличивается до 2
print(sys.getrefcount(a))  # Выведет 3 (временная ссылка при вызове функции)

del b   # Счетчик уменьшается до 1
# Когда счетчик достигает 0, память немедленно освобождается

Преимущества:

  • Немедленное освобождение памяти
  • Минимальные накладные расходы
  • Предсказуемость

Недостатки:

  • Не может обнаружить циклические ссылки

2. Циклический сборщик мусора

Решает проблему циклических ссылок, используя алгоритм "поколений" (generational):

class Node:
    def __init__(self):
        self.parent = None
        self.children = []

# Создаем циклическую ссылку
node1 = Node()
node2 = Node()
node1.children.append(node2)
node2.parent = node1

# Даже если удалить внешние ссылки, счетчик не обнулится
del node1
del node2
# Здесь вступает в работу Generational GC

Как работает Generational GC

Три поколения объектов

  1. Generation 0 - новые объекты
  2. Generation 1 - объекты, пережившие одну сборку мусора
  3. Generation 2 - долгоживущие объекты
import gc
print(gc.get_threshold())  # Покажет пороги для каждого поколения (700, 10, 10)

Алгоритм работы

  1. При создании объекта он попадает в Generation 0
  2. Когда число объектов в Generation 0 превышает порог, запускается сборка мусора
  3. Выжившие объекты перемещаются в Generation 1
  4. Процесс повторяется для старших поколений с большими порогами

Ручное управление GC

import gc

# Отключение автоматического GC (не рекомендуется)
gc.disable()

# Принудительный запуск сборки мусора
gc.collect()  # Для всех поколений
gc.collect(0)  # Только Generation 0

# Настройка параметров
gc.set_threshold(1000, 15, 15)  # (threshold0, threshold1, threshold2)

Особые случаи

1. __del__ и циклические ссылки

Метод __del__ может помешать сборке мусора:

class A:
    def __del__(self):
        print("Удаляется A")

class B:
    def __del__(self):
        print("Удаляется B")

a = A()
b = B()
a.b = b
b.a = a  # Циклическая ссылка

del a
del b
# Объекты не будут удалены из-за наличия __del__ и циклической ссылки

2. Weak References

Альтернатива для избежания циклических ссылок:

import weakref

class Node:
    def __init__(self):
        self.parent = None
        self._children = weakref.WeakSet()

    def add_child(self, child):
        self._children.add(child)
        child.parent = self

Отладка проблем с памятью

import gc

# Получение списка "подозрительных" объектов
gc.set_debug(gc.DEBUG_LEAK)  # Включение режима отладки
gc.collect()

# Анализ объектов в памяти
for obj in gc.get_objects():
    if isinstance(obj, SomeClass):
        print(f"Обнаружен объект: {obj}")

Резюмируем

  1. Основной механизм - счетчик ссылок (быстрый и предсказуемый)
  2. Дополнительный механизм - Generational GC (решает проблему циклических ссылок)
  3. Три поколения объектов с разными порогами сборки
  4. Ручное управление возможно, но обычно не требуется
  5. Особые случаи:
    • Метод __del__ может помешать сборке мусора
    • WeakRefs помогают избегать циклических ссылок
  6. Инструменты отладки доступны через модуль gc

Python автоматически управляет памятью, но понимание работы GC помогает писать более эффективный код и избегать утечек памяти.