Как работает garbage collector python

Обновлено: 17.05.2024

В Python диспетчер памяти отвечает за такие задачи, периодически выполняя очистку, выделение и управление памятью. В отличие от C, Java и других языков программирования, Python управляет объектами с помощью подсчета ссылок. Это означает, что диспетчер памяти отслеживает количество ссылок на каждый объект в программе. Когда счетчик ссылок объекта падает до нуля, что означает, что объект больше не используется, сборщик мусора (часть диспетчера памяти) автоматически освобождает память от этого конкретного объекта.

Пользователю не нужно беспокоиться об управлении памятью, поскольку процесс выделения и удаления памяти полностью автоматизирован. Восстановленная память может использоваться другими объектами.

Python Garbage Collection (Уборка мусора)

Как объяснялось ранее, Python удаляет объекты, на которые больше нет ссылок в программе, чтобы освободить пространство памяти. Этот процесс, в котором Python освобождает блоки памяти, которые больше не используются, называется Garbage Collection. Python Garbage Collector (GC) работает во время выполнения программы и запускается, если счетчик ссылок уменьшается до нуля. Счетчик ссылок увеличивается, если объекту присваивается новое имя или он помещается в контейнер, такой как кортеж или словарь. Аналогично, счетчик ссылок уменьшается, когда ссылка на объект переназначается, когда ссылка на объект выходит из области видимости или когда объект удаляется.

Примеры, когда количество ссылок увеличивается:

  • оператор присваивания
  • передача аргументов
  • вставка нового объекта в лист (увеличивается количество ссылок для объекта)
  • конструция вида foo = bar (foo начинается ссылаться на тот же объект, что и bar)

Память представляет собой кучу, которая содержит объекты и другие структуры данных, используемые в программе. Выделение и перераспределение этого пространства кучи контролируется менеджером памяти Python с помощью функций API.

Объекты Python в памяти

Каждая переменная в Python действует как объект. Объекты могут быть простыми (содержащими числа, строки и т. д.) Или контейнерами (словарями, списками или пользовательскими классами). Кроме того, Python является динамически типизированным языком, что означает, что нам не нужно объявлять переменные или их типы перед их использованием в программе.

Если вы посмотрите на первые 2 строки вышеуказанной программы, объект x известен. Если мы удалим объект x и пытаемся его использовать, мы получим ошибку, в которой говорится, что переменная x не определена.

Вы можете видеть, что сборка мусора в Python полностью автоматизирована, и программисту не нужно беспокоиться об этом, в отличие от таких языков, как C.

Модификация Garbage Collector

Более ранние поколения также собирают мусор чаще, чем высшие поколения. Это связано с тем, что более новые объекты чаще отбрасываются, чем старые.

Модуль gc включает функции для изменения порогового значения, ручного запуска процесса сбора мусора, отключения процесса сбора мусора и т. д. Мы можем проверить пороговые значения разных поколений сборщика мусора с помощью метода get_threshold():

Пример вывода:

Как видите, здесь у нас есть порог 700 для первого поколения и 10 для каждого из двух других поколений.

Мы можем изменить пороговое значение для запуска процесса сбора мусора, используя метод set_threshold() модуля gc:

В приведенном выше примере мы увеличили пороговое значение для всех 3 поколений. Увеличение порогового значения уменьшит частоту работы сборщика мусора. Обычно нам не нужно слишком много думать о сборке мусора в Python, но это может быть полезно при оптимизации времени выполнения программ для вашей целевой системы. Одним из ключевых преимуществ является то, что механизм сборки мусора в Python автоматически обрабатывает множество низкоуровневых деталей.

Зачем выполнять сборку мусора вручную?

Мы знаем, что интерпретатор Python отслеживает ссылки на объекты, используемые в программе. В более ранних версиях Python (до версии 1.6) интерпретатор Python использовал только механизм подсчета ссылок для обработки памяти. Когда количество ссылок падает до нуля, интерпретатор Python автоматически освобождает память. Этот классический механизм подсчета ссылок очень эффективен, за исключением того, что он не работает, когда в программе есть циклические ссылок. Зацикливание ссылок происходит, если один или несколько объектов ссылаются друг на друга, и, следовательно, счетчик ссылок никогда не достигает нуля.

Давайте рассмотрим пример.

Приведенный выше код создает ссылочный цикл, где объект list ссылается на себя. Следовательно, память для объект list не будет освобождена автоматически, когда завершится выполнение функции. Проблема зацикливания не может быть решена путем подсчета ссылок. Однако эту проблему можно решить, изменив поведение сборщика мусора в вашем приложении Python.

Для этого мы можем использовать функцию gc.collect() модуля gc.

gc.collect() возвращает количество объектов, которые были собраны и удалены.

Существует два способа выполнения сборки мусора вручную: сборка мусора на основе времени или события.

Основанная на времени сборка мусора довольно проста: функция gc.collect() вызывается через фиксированный интервал времени.

Сборка мусора на основе событий вызывает функцию gc.collect() после того, как происходит какое либо событие (т.е. когда приложение закрывается или приложение остается бездействующим в течение определенного периода времени).

Давайте разберемся с ручной сборкой мусора, создав несколько циклов.

На экране отобразится следующее:

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

В приведенном выше коде, поскольку число ссылок равно по крайней мере 1 и никогда не может достигнуть 0, мы принудительно собирали объекты с garbage collected, вызывая gc.collect(). Тем не менее, помните не форсируйте сборку мусора слишком часто. Причина в том, что даже после освобождения памяти GC тратит время на оценку пригодности объекта для сбора мусора, занимая процессорное время и ресурсы. Кроме того, не забудьте вручную управлять сборщиком мусора только после того, как ваше приложение полностью запустится.

Заключение

В этой статье мы обсудили, как управление памятью в Python обрабатывается автоматически с помощью стратегий подсчета ссылок и сбора мусора. Без garbage collection реализация успешного механизма управления памятью в Python невозможна. Кроме того, программистам не нужно беспокоиться об удалении выделенной памяти, так как об этом заботится менеджер памяти Python. Это приводит к уменьшению утечек памяти и повышению производительности.

Физическая архитектура

Эта секция поможет понять code flow.

Когда у пользовательского потока заканчивается память, он может получить свободное пространство с помощью функции try_allocate_more_space .

Функция try_allocate_more_space вызывает GarbageCollectGeneration , когда нужно запустить сборщик мусора.

Если сборка мусора в режиме рабочей станции производится непараллельно, GarbageCollectGeneration выполняется в пользовательском потоке, который вызвал сборщик мусора. Поток кода выглядит следующим образом:

Если выполняется параллельная сборка мусора в режиме рабочей станции (по умолчанию), поток кода для фоновой сборки мусора выглядит так:

Функция collect()

Как мы изучили выше, сборка мусора запускается автоматически, когда программа находится в процессе выполнения, иногда нам может потребоваться запустить сборку мусора в определенное время. Мы можем сделать это, вызвав функцию collect(). Давайте попробуем определить класс LinkedList, чтобы продемонстрировать это:

Как только это будет сделано, мы можем начать создавать их объекты и запускать сборку мусора вручную:

Циклические объекты LinkedList

В этом примере циклические объекты LinkedList очищаются, как только сборщик мусора запускается в первый раз.

Выбор поколения для сборки мусора

При активации сборщик должен определить, в каком поколении проводить сборку. Кроме порогового значения, на этот выбор влияют и другие факторы:

  • фрагментация поколения – если поколение сильно фрагментировано, сбор мусора в нём, скорее всего, будет продуктивным;
  • если память машины слишком загружена, сборщик может провести более глубокую очистку, если такая очистка с большой долей вероятности освободит пространство и позволит избежать ненужной подкачки страниц (памяти во всей машине);
  • если в эфемерном сегменте заканчивается место, сборщик может провести в этом сегменте более глубокую очистку (собрать больше объектов поколения 1), чтобы избежать выделения нового сегмента кучи.

Процесс сборки мусора

Этап маркировки

Во время этого этапа CLR должна найти все живые объекты.

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

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

Этап планирования

На этом этапе моделируется сжатие, чтобы определить его эффективность. Если результат оказывается продуктивным, сборщик начинает фактическое сжатие. В противном случае он просто производит уборку.

Этап перемещения

Если сборщик выполняет сжатие, это приведёт к перемещению объектов. В этом случае необходимо обновить ссылки на эти объекты. Во время этапа перемещения сборщик должен найти все ссылки, указывающие на объекты в тех поколениях, где проводится сборка мусора. Напротив, во время стадии маркировки сборщик помечает только живые объекты, поэтому ему не нужно рассматривать слабые ссылки.

Этап сжатия

Этот этап достаточно прост, поскольку сборщик уже определил новые адреса для перемещения объектов во время этапа планирования. При сжатии объекты будут скопированы по этим адресам.

Этап уборки

Во время этого этапа сборщик ищет неиспользуемое пространство между живыми объектами. Вместо этого пространства он создаёт свободные объекты. Неиспользуемые объекты, находящиеся рядом, становятся одним свободным объектом. Все свободные объекты помещаются в список свободных объектов.

Code flow

  • WKS GC: Сборка мусора в режиме рабочей станции
  • SVR GC: Сборка мусора в режиме сервера
Функциональное поведение
WKS GC без параллельной сборки мусора
  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Сборщик вызывает SuspendEE , чтобы приостановить все управляемые потоки.
  3. Сборщик выбирает поколение для очистки.
  4. Начинается маркировка объектов.
  5. Сборщик переходит на этап планирования и определяет необходимость сжатия.
  6. При необходимости сборщик перемещает объекты и выполняет сжатие. В другом случае – просто выполняет уборку.
  7. Сборщик вызывает RestartEE , чтобы вновь запустить управляемые потоки.
  8. Пользовательские потоки продолжают работу.
WKS GC с параллельной сборкой мусора

Этот алгоритм описывает фоновую сборку мусора.

  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Сборщик вызывает SuspendEE , чтобы приостановить все управляемые потоки.
  3. Сборщик определяет, нужно ли запустить фоновую сборку мусора.
  4. Если да, активируется поток фоновой сборки мусора. Этот поток вызывает RestartEE , чтобы возобновить управляемые потоки.
  5. Выделение памяти для управляемых процессов продолжается одновременно с фоновой сборкой мусора.
  6. Пользовательский поток может использовать всю выделенную для него память и запустит эфемерную сборку мусора (также известную как высокоприоритетная сборка мусора). Она выполняется так же, как в режиме рабочей станции без параллельной сборки мусора..
  7. Поток фоновой сборки мусора снова вызывает SuspendEE , чтобы завершить маркировку, а затем вызывает RestartEE , чтобы запустить параллельную уборку с работающими пользовательскими потоками.
  8. Фоновая сборка мусора завершается.
SVR GC без параллельной сборки мусора
  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Потоки сборки мусора в режиме сервера активируются и вызывают SuspendEE , чтобы приостановить выполнение управляемых потоков.
  3. Потоки сборки мусора в режиме сервера выполняют те же операции, что и в режиме рабочей станции без параллельной сборки мусора.
  4. Потоки сборки мусора в режиме сервера вызывают RestartEE , чтобы запустить управляемые потоки.
  5. Пользовательские потоки продолжают работу.
SVR GC с параллельной сборкой мусора

Алгоритм такой же, как и в случае с параллельной сборкой мусора в режиме рабочей станции, только нефоновая сборка выполняется в серверных потоках.

Всё, что нужно знать о сборщике мусора в Python

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

Менеджер памяти

В отличие от других популярных языков, Python не освобождает всю память обратно операционной системе как только он удаляет какой либо объект. Вместо этого, он использует дополнительный менеджер памяти, предназначенный для маленьких объектов (размер которых меньше чем 512 байт). Для работы с такими объектами он выделяет большие блоки памяти, в которых в дальнейшем будет хранится множество маленьких объектов.

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

Таким образом, если долгоживущий Python процесс с течением времени начинает потреблять больше памяти, то это совсем не означает, что в вашем коде есть проблема с утечкой памяти. Если вы хотите узнать больше о менеджере памяти в Python, вы можете прочитать об этом в другой моей статье (англ.).

Алгоритмы сборки мусора

Стандартный интерпретатор питона (CPython) использует сразу два алгоритма, подсчет ссылок и generational garbage collector (далее GC), более известный как стандартный модуль gc из Python.

Алгоритм подсчета ссылок очень простой и эффективный, но у него есть один большой недостаток. Он не умеет определять циклические ссылки. Именно из-за этого, в питоне существует дополнительный сборщик, именуемый поколенческий GC, который следит за объектами с потенциальными циклическими ссылками.

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

Алгоритм подсчета ссылок

Алгоритм подсчета ссылок это одна из самых простых техник для сборки мусора. Объекты удаляются как только на них больше нет ссылок.

В Python, переменные не хранят значения, а выступают в роли ссылок на объекты. То есть когда вы присваивайте значение новой переменной, то сначала создается объект с этим значением, а уже потом переменная начинает ссылаться на него. На один объект может ссылаться множество переменных.

Каждый объект в Python содержит дополнительное поле (счетчик ссылок), в котором хранится количество ссылок на него. Как только кто-то ссылается на объект, это поле увеличивается на единицу. Если по какой-то причине ссылка пропадает, то это поле уменьшается на один.

Примеры, когда количество ссылок увеличивается:

  • оператор присваивания
  • передача аргументов
  • вставка нового объекта в лист (увеличивается количество ссылок для объекта)
  • конструция вида foo = bar (foo начинается ссылаться на тот же объект, что и bar)

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

Переменные, которые объявлены вне функций, классов и блоков называются глобальными. Как правило, жизненный цикл таких переменных равен жизни Python процесса. Таким образом, количество ссылок на объекты на которые ссылаются глобальные переменные никогда не падает до нуля.

Переменные, которые объявлены внутри блока (функции, класса) имеют локальную видимость (т.е. они видны только внутри блока). Как только интерпретатор питона выходит из блока он уничтожает все ссылки созданные локальными переменными внутри него.

Вы всегда можете проверить количество ссылок используя функцию sys.getrefcount .

Пример работы счетчика ссылок:


Основная причина, из-за которой стандартный интерпретатор (CPython) использует счетчик ссылок, является исторической. В настоящее время можно встретить множество дебатов по поводу данного подхода. Некоторые люди считают, что сборщик мусора может быть намного эффективней без участия алгоритма подсчета ссылок. У данного алгоритма есть множество проблем, таких как циклические ссылки, блокирование потоков, а также дополнительные накладные расходы на память и cpu.

Основным плюсом этого алгоритма является то, что объекты удаляются сразу как только они не нужны.

Дополнительный сборщик мусора

Зачем нам нужен дополнительный алгоритм, когда у нас уже есть подсчет ссылок?

К сожалению, классический алгоритм подсчета ссылок имеет один большой недостаток — он не умеет находить циклические ссылки. Циклические ссылки происходят когда один или более объектов ссылаются на друг друга.

Как вы можете видеть, объект lst ссылается сам на себя, тогда как object1 и object2 ссылаются друг на друга. Для таких объектов счетчик ссылок всегда будет равен 1.

Демонстрация на Python:


В примере выше, инструкция del удаляет ссылки на наши объекты (не сами объекты). Как только Python выполняет инструкцию del эти объекты становятся недоступны из Python кода. Однако, с выключенным модулем gc они по прежнему будут оставаться в памяти, т.к. они имели циклические ссылки и их счетчик по прежнему равен единице. Вы можете визуально исследовать такие связи используя библиотеку objgraph .

Циклические ссылки могут происходить только в “контейнерных” объектах. Т.е. в объектах, которые могут хранить другие объекты, например в списках, словарях, классах и кортежах. GC не следит за простыми и неизменяемыми типами, за исключением кортежей. Некоторые кортежи и словари так же исключаются из списка слежки при выполнении определенных условий. Со всеми остальными объектами гарантированно справляется алгоритм подсчета ссылок.

Когда GC срабатывает

В отличие от алгоритма подсчета ссылок, циклический GC не работает в режиме реального времени и запускается периодически. Каждый запуск сборщика создаёт микропаузы в работе кода, поэтому CPython (стандартный интерпретатор) использует различные эвристики, для определения частоты запуска сборщика мусора.

Циклический сборщик мусора разделяет все объекты на 3 поколения (генерации). Новые объекты попадают в первое поколение. Если новый объект выживает процесс сборки мусора, то он перемещается в следующее поколение. Чем выше поколение, тем реже оно сканируется на мусор. Так-как новые объекты зачастую имеют очень маленький срок жизни (являются временными), то имеет смысл опрашивать их чаще, чем те, которые уже прошли через несколько этапов сборки мусора.

В каждой генерации есть специальный счетчик и порог срабатывания, при достижении которых срабатывает процесс сборки мусора. Каждый счетчик хранит количество аллокаций минус количество деаллокаций в данной генерации. Как только в Python создается какой либо контейнерный объект, он проверяет эти счетчики. Если условия срабатывают, то начинается процесс сборки мусора.

Если сразу несколько или больше поколений преодолели порог, то выбирается наиболее старшее поколение. Это сделано из-за того, что старые поколения также сканируют все предыдущие. Чтобы сократить число пауз сборки мусора для долгоживущих объектов, самая старшая генерация имеет дополнительный набор условий.

Стандартные пороги срабатывания для поколений установлены на 700, 10 и 10 соответственно, но вы всегда можете их изменить с помощью функций gc.get_threshold и gc.set_threshold .

Алгоритм поиска циклов

Для полноценного описания алгоритма поиска циклов потребуется отдельная статья. Если кратко, то GC итерирует каждый объект из выбранных поколений и временно удаляет все ссылки от отдельно взятого объекта (все ссылки на которые этот объект ссылается). После полного прохода, все объекты, у которых счетчик ссылок меньше двух считаются недоступными из питона и могут быть удалены.

Для более глубокого понимания я рекомендую прочитать (прим. переводчика: английский материал) оригинальное описание алгоритма от Neil Schemenauer и функцию collect из исходников CPython. Описание из Quora и пост о сборщике мусора так же могут быть полезны.

Стоит отметить, что проблема с деструкторами описанная в оригинальном описании алгоритма была исправлена начиная с Python 3.4 (подробнее в PEP 442).

Советы по оптимизации

Циклы зачастую случаются в реальных задачах, их можно встретить в задачах с графами, связными списками или в структурах данных, где требуется вести учёт отношений между объектами. Если ваша программа имеет высокую нагрузку и требовательна к задержкам, то, по возможности, циклы лучше избегать.

В местах, где вы заведомо используйте циклические ссылки, можно использовать «слабые» ссылки. Слабые ссылке реализованы в модуле weakref и в отличие от обычных ссылок никак не влияют на счётчик ссылок. Если объект со слабой ссылок оказывается удалённым, то вместо него возвращается None .

В некоторых случаях полезно отключить автоматическую сборку модулем gc и вызывать его вручную. Для этого достаточно вызывать gc.disable() и в дальнейшем вызывать gc.collect() вручную.

Как найти и отладить циклические ссылки

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

Модуль gc предоставляет вспомогательные функции, которые могут помочь в отладке. Если параметры GC установить на флаг DEBUG_SAVEALL , то все недоступные объекты будут добавлены в список gc.garbage .


Как только вы определите проблемное место — его можно визуализировать с помощью objgraph.

Заключение

Основной процесс сборки мусора выполняется алгоритмом подсчета ссылок, который очень простой и не имеет настроек. Дополнительный алгоритм используется только для поиска и удаления объектов с циклическими ссылками.

Не стоит заниматься преждевременной оптимизацией кода под сборщик мусора, на практике серьёзне проблемы со сборкой мусора встречаются довольно редко.

Garbage Collector. Полный курс + перевод из BOTR

В данной статье вы встретите сразу два источника информации:



Как сборка мусора зависит от реализации?

Сборка мусора – это механизм, который зависит от реализаций Python, таких как CPython, Jython или IronPython:

  • C Реализация использует подсчет ссылок для отслеживания недостижимых объектов. Вместо этого он не отслеживает объекты в каждой строке выполнения, а периодически выполняет алгоритм обнаружения цикла, который ищет недоступные объекты и очищает их.
  • Jython использует сборщик мусора JVM. Тоже самое относится к IronPython, который использует сборщик мусора CLR.

Несколько окончательных способов

Здесь давайте представим несколько окончательных способов оптимизации использования сборки мусора:

2. Устройство сборщика мусора by Maoni Stephens (@maoni0)

Архитектура компонентов

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

Есть и другие способы вызвать сборщик, например вручную, с помощью GC.Collect. Также поток финализатора может получить асинхронное уведомление о том, что память заканчивается (что вызовет сборщик).

Устройство распределителя

Распределитель вызывается вспомогательными компонентами среды выполнения с указанием следующей информации:

  • необходимый размер выделяемого участка;
  • контекст выделения памяти для потока исполнения;
  • флаги, которые указывают, например, является ли объект финализируемым.

Сборщик мусора не предусматривает специальных методов обработки для разных типов объектов. Он получает информацию о размере объекта от среды выполнения.

В зависимости от размера, сборщик делит объекты на две категории: маленькие (< 85 000 байт) и большие (>= 85 000 байт). В целом сборка маленьких и больших объектов может происходить одинаково. Однако сборщик разделяет их по размеру, поскольку сжатие больших объектов требует много ресурсов.

Сборщик мусора выделяет память распределителю с учётом контекстов выделения. Размер контекста выделения определяется блоками выделяемой памяти.

Контексты выделения – небольшие области определённого сегмента кучи, каждая из которых предназначена для определённого потока исполнения. На машине с одним процессором (имеется в виду 1 логический процессор) используется один контекст выделения памяти для объектов поколения 0.

Блок выделяемой памяти – объём памяти, выделяемый распределителем каждый раз, когда ему требуется больше памяти, чтобы расположить объект внутри области. Размер блока обычно составляет 8 Кб, а средний размер управляемых объектов – 35 байт. Поэтому в одном блоке можно расположить множество объектов.

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

Распределитель устроен таким образом, чтобы:

вызывать сборщик мусора, когда необходимо: распределитель вызывает сборщик, когда объём памяти, выделенной для объектов, превышает пороговое значение (установленное сборщиком), или если распределитель больше не может выделять память в данном сегменте. Пороговые значения и управляемые сегменты будут подробно описаны далее.

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

эффективно использовать кэш: распределитель выделяет память блоками, а не для каждого объекта. Он обнуляет так много памяти, чтобы подготовить кэш процессора, поскольку некоторые объекты будут размещены прямо в нём. Блок выделяемой памяти обычно равен 8 Кб.

эффективно ограничивать область, выделенную потоку исполнения: близость контекстов и блоков памяти, выделенных для потока, гарантирует, что только один поток будет записывать данные в выделенный участок пространства. В результате не нужно ограничивать выделение памяти, пока пространство в текущем контексте выделения ещё не закончилось.

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

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

С помощью указанных функций можно выделять память как для маленьких, так и больших объектов. Существует функция для выделения места прямо в куче больших объектов (LOH):

Устройство сборщика

Задачи сборщика мусора

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

  • сборка мусора должна происходить достаточно часто, чтобы не захламлять управляемую кучу большим числом (по соотношению или в абсолютном количестве) неиспользуемых объектов (мусор), под которые выделена память;
  • сборка мусора должна происходить как можно реже, чтобы не расходовать полезное процессорное время, даже при том, что более частая сборка позволит меньше использовать память;
  • сборка мусора должна быть продуктивной, поскольку если в результате сборки удалось освободить лишь небольшой участок памяти, тогда и сборка, и потраченное процессорное время были напрасны;
  • сборка мусора должна быть быстрой, поскольку выполнение многих рабочих нагрузок требует малого времени задержки;
  • разработчикам, пишущим управляемый код, не нужно много знать о сборке мусора, чтобы добиться эффективного использования памяти (по сравнению с их рабочей нагрузкой);
  • сборщик мусора должен подстроиться под разный характер использования памяти.
Логическое описание управляемой кучи

Сборщик мусора CLR собирает объекты, логически разделённые по поколениям. После сборки объектов в поколении N, оставшиеся объекты маркируются как принадлежащие поколению N+1. Этот процесс называется продвижение объектов по поколениям. В этом процессе существуют исключения, когда необходимо перевести объект в поколение ниже или не продвигать его вообще.

В случае маленьких объектов куча делится на три поколения: gen0, gen1 и gen2. Для больших объектов существует только одно поколение – gen3. Gen0 и gen1 называют эфемерными поколениями (объекты живут в них короткое время).

Для кучи небольших объектов номер поколения означает их возраст. Например, gen0 – самое молодое поколение. Это не значит, что все объекты в gen0 моложе объектов в gen1 или gen2. Здесь существуют исключения, которые описаны ниже. Сборка поколения означает сборку объектов в этом поколении, а также во всех его более молодых поколениях.

Теоретически сборка больших и маленьких объектов может происходить одинаково. Однако поскольку сжатие крупных объектов требует много ресурсов, их сборка происходит по-другому. Большие объекты содержатся только в gen2 и собираются только во время сборки мусора в этом поколении из соображений производительности. Как gen2, так и gen3 могут быть большими, а сборка объекта в эфемерных поколениях (gen0 и gen1) не должна быть слишком ресурсозатратной.

Объекты размещаются в самом младшем поколении. Для маленьких объектов это gen0, а для больших – gen3.

Физическое описание управляемой кучи

Управляемая куча состоит из набора сегментов. Сегмент – непрерывный блок памяти, который ОС передаёт сборщику мусора. Сегменты кучи разбиваются на мелкие и большие участки для размещения маленьких и больших объектов. Сегменты каждой кучи связаны вместе. По крайней мере один сегмент для маленького объекта и один для большого резервируются при загрузке CLR.

В каждой куче маленьких объектов есть только один эфемерный сегмент, где находятся поколения gen0 и gen1. Этот сегмент может содержать или не содержать объекты поколения gen2. Кроме эфемерных сегментов, могут существовать один или более дополнительных сегментов, которые будут сегментами gen2, так как они содержат объекты поколения 2.

Куча больших объектов состоит из одного или более сегментов.

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

Сегменты кучи выделяются по мере необходимости. Если они не содержат используемых объектов, сегменты удаляются. Однако начальный сегмент в куче всегда существует. За один раз для каждой кучи выделяется один сегмент. В случае маленьких объектов это происходит во время сборки мусора, а для больших – во время выделения памяти для них. Такая схема повышает производительность, поскольку большие объекты собираются только в поколении 2 (что требует много ресурсов).

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

Пороговое значение объёма выделенной памяти

Это логическое понятие, связанное с размером каждого поколения. Если он превышен, в поколении начинается сборка мусора.

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

Сборка мусора в Python

Сборка мусора (GC) в Python – это механизм управления памятью. Давайте рассмотрим различные аспекты сборки мусора и то, как это работает.

Что такое сборка мусора в Python?

Сборка мусора в Python – это процесс очистки общей памяти компьютера, которая в настоящее время используется выполняющейся программой, когда ей больше не нужна эта память. При сборке мусора этот кусок памяти очищается, чтобы другие программы (или та же программа) могли использовать его снова.

В этом уроке мы изучим, как этот механизм работает в Python.

Давайте изучим сборку мусора в Python на примере:

Модель объекта в python

Теперь мы позволяем переменным user1, user2 и user3 указывать на None вместо экземпляра User.

Пример gc в python

В приведенном выше коде ссылки изменились на:

Управление памятью в python

После того, как мы присвоили последней переменной user3 значение None, объект собирается сборщиком мусора, и это вызывает функцию __del__.

Читайте также: