PHPerKaigi 2024

Сбор циклических ссылок

Обычно механизмы подсчёта ссылок в памяти, например, которые работали в 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_THRESHOLD_DEFAULT в файле Zend/zend_gc.c в исходном коде PHP и пересобрав PHP). Если сборщик мусора выключен, алгоритм никогда не будет запущен. Тем не менее, буфер всегда заполняется корнями.

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

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

Кроме изменения параметра zend.enable_gc, механизм сборки мусора можно запустить и остановить, последовательно вызвав функции gc_enable() и gc_disable(). Вызов этих функций имеет тот же эффект, что и включение/выключение механизма в настройках конфигурации. Кроме того, можно запустить сборку мусора, даже если корневой буфер ещё не заполнен. Для этого вы можете вызвать функцию gc_collect_cycles(), которая также возвращает количество циклических ссылок собранных алгоритмом.

Причиной включения и выключения механизма сборки, а также его ручного запуска, может стать то, что некоторые части вашего приложения могут быть требовательными ко времени. В этих случаях, возможно, не захочется постороннего вмешательства сборщика мусора. Разумеется, выключая сборщик мусора в определённых местах приложения, есть риск получить утечку памяти, т. к. потенциально некоторые корни могут не поместиться в ограниченный корневой буфер. Более целесообразно будет вызвать функцию gc_collect_cycles() прямо перед вызовом функции gc_disable() для освобождения памяти и уже записанных корней в буфере. Это очистит буфер и даст больше места для хранения корней, пока механизм будет выключен.

add a note

User Contributed Notes 3 notes

up
18
Dallas
5 years ago
After testing, breaking up memory intensive code into a separate function allows the garbage collection to work.

For example the original code was like:-
while(true){
//do memory intensive code
}

can be turned into something like:-
function intensive($parameters){
//do memory intensive code
}

while(true){
intensive($parameters);
}
up
12
Yousha dot A at Hotmail dot com
8 years ago
Memory leak: meaning you keep a reference to it thus preventing the GC from collecting it.
up
9
Yousha dot A at Hotmail dot com
7 years ago
── Unused Objects ─── ─ In use Objects
↓ ↓ ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
▲ ▲
Unreferenced Referenced
Objects Objects

█ Memory leak
To Top