Garbage Collection: как работает сборщик мусора в Python
В этом уроке вы узнаете, как работает сборщик мусора Python и как программно взаимодействовать с garbage collector.
Управление памятью и сборка мусора в Python
В C/C++ вы несете полную ответственность за управление памятью программы. Однако в Python вам не нужно самостоятельно управлять памятью, поскольку Python делает это за вас автоматически.
Из руководства по ссылкам вы узнали, что диспетчер памяти Python отслеживает ссылки на объекты. Диспетчер памяти уничтожает объект и освобождает память, как только счетчик ссылок этого объекта достигает нуля.
Однако подсчет ссылок не всегда работает должным образом. Например, когда у вас есть объект, который ссылается сам на себя, или два объекта ссылаются друг на друга. Это создает нечто, называемое циклическими ссылками. Когда диспетчер памяти Python не может удалить объекты с циклическими ссылками, это вызывает утечку памяти.
Вот почему сборщик мусора вступает в игру для исправления циклических ссылок. Python позволяет взаимодействовать со сборщиком мусора через встроенный модуль gc.
Взаимодействие со сборщиком мусора Python
В этом примере мы сначала создадим циклическую ссылку между двумя экземплярами класса A и класса B. Затем мы используем сборщик мусора для уничтожения объектов в циклической ссылке.
- Сначала импортируем модули gc и ctypes, определяем две функции для подсчета ссылок и проверяем, существует ли объект в памяти:
import gc import ctypes def ref_count(address): return ctypes.c_long.from_address(address).value def object_exists(object_id): for object in gc.get_objects(): if id(object) == object_id: return True return False
В этом коде ref_count() возвращает счетчик ссылок объекта, указанного его адресом в памяти. А функция object_exists() возвращает True, если объект существует в памяти.
- Во-вторых, создайте два класса A и B, которые ссылаются друг на друга:
class A: def __init__(self): self.b = B(self) print(f'A: {hex(id(self))}, B: {hex(id(self.b))}') class B: def __init__(self, a): self.a = a print(f'B: {hex(id(self))}, A: {hex(id(self.a))}')
- В-третьих, отключите сборщик мусора, вызвав функцию Disable():
gc.disable()
- В-четвертых, создайте новый экземпляр класса A, который также автоматически создаст новый экземпляр класса B:
a = A()
Выход:
B: 0x20fccd148e0, A: 0x20fccd75c40 A: 0x20fccd75c40, B: 0x20fccd148e0
- В-пятых, определите две переменные для хранения адресов памяти экземпляров A и B. Эти переменные отслеживают адреса памяти экземпляров A и B, когда переменная a ссылается на другой объект.
a_id = id(a) b_id = id(a.b)
- В-шестых, покажите количество ссылок экземпляров A и B:
print(ref_count(a_id)) # 2 print(ref_count(b_id)) # 1
Экземпляр A имеет две ссылки: переменную a и экземпляр B. И экземпляр B имеет одну ссылку, которая является экземпляром A.
- В-седьмых, проверьте, находятся ли в памяти оба экземпляра A и B:
print(object_exists(a_id)) # True print(object_exists(b_id)) # True
Оба они существуют.
- В-восьмых, установите для переменной значение None:
a = None
- В-девятых, получите счетчики ссылок экземпляров A и B:
print(ref_count(a_id)) # 1 print(ref_count(b_id)) # 1
Теперь оба счетчика ссылок экземпляра A и B равны 1.
- В-десятых, проверьте, существуют ли экземпляры:
print(object_exists(a_id)) # True print(object_exists(b_id)) # True
Оба они все еще существуют, как и ожидалось.
- Одиннадцатое, запустите сборщик мусора:
gc.collect()
Когда сборщик мусора запускается, он может обнаружить циклическую ссылку, уничтожить объекты и освободить память.
- Двенадцатое, проверьте, существуют ли экземпляры A и B:
print(object_exists(a_id)) # False print(object_exists(b_id)) # False
Они оба больше не существуют из-за сборщика мусора.
- В-тринадцатых, получите счетчики ссылок экземпляров A и B:
print(ref_count(a_id)) # 0 print(ref_count(b_id)) # 0
Объединим все это вместе.
import gc import ctypes def ref_count(address): return ctypes.c_long.from_address(address).value def object_exists(object_id): for object in gc.get_objects(): if id(object) == object_id: return True return False class A: def __init__(self): self.b = B(self) print(f'A: {hex(id(self))}, B: {hex(id(self.b))}') class B: def __init__(self, a): self.a = a print(f'B: {hex(id(self))}, A: {hex(id(self.a))}') # disable the garbage collector gc.disable() a = A() a_id = id(a) b_id = id(a.b) print(ref_count(a_id)) # 2 print(ref_count(b_id)) # 1 print(object_exists(a_id)) # True print(object_exists(b_id)) # True a = None print(ref_count(a_id)) # 1 print(ref_count(b_id)) # 1 print(object_exists(a_id)) # True print(object_exists(b_id)) # True # run the garbage collector gc.collect() # check if object exists print(object_exists(a_id)) # False print(object_exists(b_id)) # False # reference count print(ref_count(a_id)) # 0 print(ref_count(b_id)) # 0