Сбор циклических ссылок
Традиционно механизмы подсчёта ссылок в памяти вроде тех, которые работали в PHP раньше,
не справлялись с утечками памяти, которые вызывали циклические ссылки;
однако с PHP 5.3.0 разработчики языка реализовали синхронный алгоритм из исследования
» Concurrent Cycle Collection in Reference Counted Systems
(англ. «Параллельный сбор циклических ссылок в системах подсчёта ссылок»), который решает эту проблему.
Полное описание работы алгоритма выходит за рамки раздела, но основы раздел объясняет.
Вначале установим базовые правила. Первое, PHP продолжает хранить в памяти и не рассматривает
как мусор контейнеры, значение бита refcount которых увеличилось.
Zval-контейнер, количество ссылок в котором уменьшилось до нуля,
освобождается из памяти. Поэтому мусорные циклы — утечки памяти из-за циклических ссылок —
появляются только при уменьшении аргумента refcount до ненулевого значения.
Второе, PHP умеет обнаруживать мусорные части в мусорных циклах путём
уменьшения количества ссылок в контейнерах на единицу и проверки, в каких
zval-контейнерах количество ссылок стало нулевым.
Алгоритм помещает возможные корни — zval-контейнеры — в «корневой буфер» и помечает
корни «фиолетовыми», чтобы не вызывать проверку мусорных циклов при каждом уменьшении
счётчика ссылок. Алгоритм следит и за тем, чтобы каждый возможный мусорный корень
попадал в буфер только один раз. Механизм сборки мусора для каждого zval-контейнера внутри буфера
стартует, только когда корневой буфер заполняется. Графически поведение показывает шаг A на рисунке выше.
На шаге B алгоритм выполняет поиск в глубину по каждому возможному корню,
чтобы однократно уменьшить количество ссылок в каждом контейнере на единицу,
и помечает корни «серыми». На шаге C алгоритм снова выполняет поиск в глубину
от каждого корневого узла, чтобы ещё раз проверить количество ссылок для каждого zval-контейнера.
Алгоритм помечает корни с нулевым количеством ссылок «белыми» (на рисунке — синим).
А если количество ссылок в контейнере больше нуля, начиная с этого корня поиск идёт в глубину
с обратным увеличением количества ссылок на единицу и пометкой корней «черными».
На последнем шаге, D, алгоритм обходит корневой буфер
и удаляет из него корни контейнеров. Алгоритм заодно проверяет, какие zval-контейнеры
на предыдущем шаге он пометил «белыми». Каждый «белый» zval-контейнер освобождается из памяти.
Теперь, когда есть базовое представление о работе алгоритма, вернёмся к тому,
как алгоритм интегрируется с PHP. По умолчанию сборщик мусора PHP включён.
Параметр zend.enable_gc в файле php.ini
разрешает отключить сборку мусора.
При включённом сборщике мусора, алгоритм поиска циклических ссылок выполняется после каждого
наполнения корневого буфера. Фиксированный размер корневого буфера равняется 10 000 возможных корней,
хотя значение изменяется путём изменения значения константы GC_THRESHOLD_DEFAULT
в файле Zend/zend_gc.c
исходного кода PHP и пересборки PHP.
При выключенном сборщике мусора алгоритм поиска циклических ссылок не запускается. Однако
в коревой буфер всё равно записываются возможные корни, независимо от активации механизма
сборки мусора через параметр конфигурации.
PHP прекратит запись возможных корней,
если корневой буфер заполнится при выключенном механизме сборки мусора.
Алгоритм не будет анализировать корни, которые не записал в буфер.
Поэтому если корни окажутся мусором с циклическими ссылками, они вызовут утечку памяти,
поскольку PHP не очистит их.
Причина постоянной записи корней в буфер даже при выключенном механизме сборки мусора
состоит в том, что записать корни быстрее, чем каждый раз, когда удаётся найти корень,
проверять, включили ли механизм сборки мусора. Однако сам механизм сборки мусора
и алгоритм анализа иногда занимают много времени.
Кроме изменения параметра конфигурации zend.enable_gc,
доступен запуск механизма сборки мусора через вызов функции gc_enable()
и остановка механизма функцией gc_disable().
Вызов этих функций даёт тот же эффект, что и включение или выключение механизма
в настройках конфигурации.
Возможна также принудительная сборка мусорных циклов, даже если корневой буфер ещё не заполнился.
Для этого вызывают функцию gc_collect_cycles(), которая
возвращает количество циклических ссылок, которые собрал алгоритм.
Смысл включения и выключения механизма сборки мусора, а также ручного запуска механизма
состоит в чувствительности отдельных частей приложения
ко времени, когда автоматический запуск механизма сборки мусора не нужен.
Отключение сборщика мусора в конкретных
частях приложения создаёт риск утечки памяти, поскольку
отдельные корни не поместятся в ограниченный
корневой буфер. Поэтому лучше перед вызовом функции gc_disable()
вызвать функцию gc_collect_cycles(),
чтобы освободить память, риск потери которой возникает из-за возможных корней,
которые алгоритм уже записал в корневой буфер.
Это очистит буфер и даст больше места
для хранения корней, пока механизм сбора мусорных циклов будет выключен.