Лекция
Сразу хочу сказать, что здесь никакой воды про циклические ссылки, и только нужная информация. Для того чтобы лучше понимать что такое циклические ссылки, сборщик мусора , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend) .
циклические ссылки возникают, когда элемент массива содержит ссылку на родительский массив, а еще когда свойство объекта содержит ссылку на сам объект. Если в структуре (массив, объект) присутствует циклическая ссылка и эта структура удаляется, то до версии PHP 5.3 фактического освобождения памяти переменной не происходило, так как структура значения zvav в поле refcount__gc (количество переменных, ссылающихся на участок памяти) содержала не нулевое число. Циклическая ссылка адресовалась по удаляемому значению и значит ячейка считалась используемой, соответственно refcount__gc > 0, а освобождение происходит только для refcount__gc = 0. Из-за этого происходило неконтролируемое захламление свободной оперативной памяти и возникала проблема утечки памяти.
С версией PHP 5.3 был внедрен механизм сбора циклических ссылок , решающий проблему утечки памяти.
Следующий код запускает цикл в 30 001 шаг и на каждом шаге создается массив, у которого второй элемент ссылается на родительский массив, создавая циклическую ссылку. Затем массив уничтожается функцией unset. На нулевом и далее на каждом 2500-ом шаге выводится количество занимаемой скриптом оперативной памяти.
Как мы видим, сначала память увеличивается, а затем резко уменьшается. И так несколько раз
Такое поведение памяти происходит из-за особенности работы сборки циклических ссылок. Об этом говорит сайт https://intellect.icu . Когда счетчик ссылок refcount__gc уменьшается (при разрыве связи со ссылкой и в случае удаления переменной), сборщик помечает потенциальную циклическую структуру zvav специальным буферным флагом, что означает нахождение структуры в корневом буфере. В терминологии алгоритма сборки содержимое буфера называется корнями. По факту наполнения буфера корнями в количестве 10 000 штук запускается
сборщик мусора . Объем буфера настраивается в исходных кодах PHP в константе GC_ROOT_BUFFER_MAX_ENTRIES.
Обходя буфер, сборщик проверяет является ли корень мусорным или это живое использующееся значение. Проверка осуществляется по алгоритму, описанному в докладе «Concurrent Cycle Collection in Reference Counted Systems», представленный в 2001 году на Европейской конференции по объектно-ориентированному программированию.
Обычно механизмы подсчета ссылок в памяти, например, используемый в PHP ранее, не решают проблему утечки памяти из-за циклических ссылок. Начиная с версии 5.3.0, в PHP реализован синхронный механизм из исследования "» Concurrent Cycle Collection in Reference Counted Systems", в котором рассматривается этот вопрос.
Полное описание работы алгоритма выходит за рамки данного раздела, поэтому приведены только основы. Прежде всего мы должны задать несколько основных правил. Если счетчик ссылок увеличивается, то контейнер все еще используется и не является мусором. Если счетчик уменьшается до нуля, то zval может быть удален. Исходя из этих правил утечки памяти с циклическими ссылками могут получиться только при уменьшении счетчика ссылок до ненулевого значения. Затем, в выделенных контейнерах можно найти мусор проверив возможность уменьшения всех счетчиков ссылок на единицу и определив те контейнеры, у которых счетчик станет равным нулю.
Для избежания постоянной проверки на мусор с циклическими ссылками при каждом уменьшении счетчика ссылок, алгоритм добавляет все возможные корни (zval контейнеры) в "корневой буфер" (помечая их как "фиолетовые"). Это также гарантирует попадание любого корня в буфер только один раз. Механизм сборки мусора стартует только тогда, когда наполняется буфер (см. шаг A на рисунке выше).
На шаге B алгоритм производит поиск в глубину по всем возможным корням для однократного уменьшения счетчика ссылок на единицу у всех контейнеров (помечая их как "серые"). На шаге C алгоритм снова производит поиск в глубину для проверки счетчиков ссылок. Если он находит счетчик с нулевым значением, то контейнер помечается как "белый" (на рисунке отображено синим). Если же счетчик больше нуля, то происходит поиск в глубину от этого контейнера с обратным увеличением счетчиков на единицу и повторной пометкой как "черный" на их контейнерах. На последнем шаге D алгоритм проходит по корневому буферу и удаляет из него корни контейнеров, заодно проверяя какие контейнеры помечены как "белые". Эти контейнеры будут освобождены из памяти.
Теперь, когда вы имеете представление о работе алгоритма, рассмотрим его интеграцию в PHP. По умолчанию сборщик мусора всегда включен. Для изменения этой опции используется параметр zend.enable_gc в php.ini.
Если сборщик мусора включен, алгоритм поиска циклических ссылок выполняется каждый раз, когда корневой буфер наполняется 10,000 корнями (вы можете поменять это значение, изменив константу GC_ROOT_BUFFER_MAX_ENTRIES
в файле Zend/zend_gc.c
в исходном коде PHP и пересобрав PHP). Если сборщик мусора выключен, алгоритм никогда не будет запущен. Тем не менее, буфер всегда заполняется корнями.
Если буфер заполнился при выключенном механизме сборки мусора, то другие корни не будут в него записаны. Таким образом, если они окажутся мусором с циклическими ссылками, то никогда не будут очищены и создадут утечку памяти.
Причиной постоянной записи корней в буфер даже при выключенном механизме сборки мусора является то, что это намного быстрее, чем постоянно проверять включен ли механизм сборки мусора или нет. Однако, сама сборка мусора и алгоритм ее анализа могут занимать значительное время.
Помимо изменения параметра zend.enable_gc, механизм сборки мусора также можно запустить и остановить вызвав функции gc_enable() и gc_disable() соответственно. Вызов этих функций имеет тот же эффект, что и включение/выключение механизма с помощью настроек конфигурации. Кроме того, можно запустить сборку мусора, даже если корневой буфер еще не заполнен. Для этого вы можете вызвать функцию gc_collect_cycles(), которая также возвращает количество циклических ссылок собранных алгоритмом.
Причиной включения и выключения механизма сборки, а также его ручного запуска, может стать то, что некоторые части вашего приложения могут быть требовательными ко времени. В этих случаях вы, возможно, не захотите постороннего вмешательства сборщика мусора. Разумеется, выключая сборщик мусора в определенных местах вашего приложения вы рискуете получить утечку памяти, т.к. потенциально некоторые корни могут не поместиться в ограниченный корневой буфер. Более целесообразно будет вызвать gc_collect_cycles() непосредственно перед вызовом gc_disable() для освобождения памяти и уже записанных корней в буфере. Это очистит буфер и позволит использовать больше места для хранения корней, пока механизм будет выключен.
https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
https://www.php.net/manual/ru/features.gc.collecting-cycles.php
Статью про циклические ссылки я написал специально для тебя. Если ты хотел бы внести свой вклад в развитие теории и практики, ты можешь написать коммент или статью отправив на мою почту в разделе контакты. Этим ты поможешь другим читателям, ведь ты хочешь это сделать? Надеюсь, что теперь ты понял что такое циклические ссылки, сборщик мусора и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)
Из статьи мы узнали кратко, но содержательно про циклические ссылки
Комментарии
Оставить комментарий
Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)
Термины: Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)