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

Обычно механизмы подсчёта ссылок в памяти, например, используемый в 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() для освобождения памяти и уже записанных корней в буфере. Это очистит буфер и позволит использовать больше места для хранения корней, пока механизм будет выключен.

add a note add a note

User Contributed Notes 6 notes

up
12
Yousha dot A at Hotmail dot com
7 years ago
── Unused Objects ─── ─ In use Objects
↓                    ↓               ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
          ▲                  ▲
     Unreferenced        Referenced
       Objects             Objects

█ Memory leak
up
10
Dallas
6 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
10
Yousha dot A at Hotmail dot com
9 years ago
Memory leak: meaning you keep a reference to it thus preventing the GC from collecting it.
up
1
instatiendaweb at gmail dot com
3 years ago
//Remove ciclic array ref
foreach($a as $clave =>$borradoa){unset($a[$clave]);}
unset($a);
up
0
744976807 at qq dot com
2 years ago
在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减"1",......
-----------------------------------------------------------
文中对步骤B的描述,应该做些补充,

起初节点 zval 本身不做减 1 操作,但是如果节点 zval 中包含的符号表中有节点又指向了初始的 zval(环形引用),那么这个时候需要对节点 zval 进行减 1 操作;

参考文章:https://www.iminho.me/wiki/blog-18.html
up
-1
Jack at example dot com
3 years ago
In step B, the algorithm runs a depth-first search on all possible roots to decrease by one the refcounts of each zval it finds, making sure not to decrease a refcount on the same zval twice (by marking them as "grey")

为了防止同一个zval紫色变量删除2次,每删除一次,就标记为灰色。
To Top