Циклические ссылки и сборщик мусора
2 июня 2018
Циклические ссылки возникают, когда элемент массива содержит ссылку на родительский массив, а ещё когда свойство объекта содержит ссылку на сам объект. Если в структуре (массив, объект) присутствует циклическая ссылка и эта структура удаляется, то до версии PHP 5.3 фактического освобождения памяти переменной не происходило, так как структура значения zvav в поле refcount__gc (количество переменных, ссылающихся на участок памяти) содержала не нулевое число. Циклическая ссылка адресовалась по удаляемому значению и значит ячейка считалась используемой, соответственно refcount__gc > 0, а освобождение происходит только для refcount__gc = 0. Из-за этого происходило неконтролируемое захламление свободной оперативной памяти и возникала проблема утечки памяти.
С версией PHP 5.3 был внедрён механизм сбора циклических ссылок (https://www.php.net/manual/ru/features.gc.collecting-cycles.php), решающий проблему утечки памяти.
Следующий код запускает цикл в 30 001 шаг и на каждом шаге создаётся массив, у которого второй элемент ссылается на родительский массив, создавая циклическую ссылку. Затем массив уничтожается функцией unset. На нулевом и далее на каждом 2500-ом шаге выводится количество занимаемой скриптом оперативной памяти.
Как мы видим, сначала память увеличивается, а затем резко уменьшается. И так несколько раз
Такое поведение памяти происходит из-за особенности работы сборки циклических ссылок. Когда счётчик ссылок refcount__gc уменьшается (при разрыве связи со ссылкой и в случае удаления переменной), сборщик помечает потенциальную циклическую структуру zvav специальным буферным флагом, что означает нахождение структуры в корневом буфере. В терминологии алгоритма сборки содержимое буфера называется корнями. По факту наполнения буфера корнями в количестве 10 000 штук запускается сборщик мусора. Объём буфера настраивается в исходных кодах PHP в константе GC_ROOT_BUFFER_MAX_ENTRIES.
Обходя буфер, сборщик проверяет является ли корень мусорным или это живое использующееся значение. Проверка осуществляется по алгоритму, описанному в докладе «Concurrent Cycle Collection in Reference Counted Systems», представленный в 2001 году на Европейской конференции по объектно-ориентированному программированию.
С версией PHP 5.3 был внедрён механизм сбора циклических ссылок (https://www.php.net/manual/ru/features.gc.collecting-cycles.php), решающий проблему утечки памяти.
Следующий код запускает цикл в 30 001 шаг и на каждом шаге создаётся массив, у которого второй элемент ссылается на родительский массив, создавая циклическую ссылку. Затем массив уничтожается функцией unset. На нулевом и далее на каждом 2500-ом шаге выводится количество занимаемой скриптом оперативной памяти.
//Использовано памяти на текущий момент
$current_memory_usage = memory_get_usage();
for($i=0; $i <= 30000; $i++)
{
$a = array(
1 => 'Тест',
2 => null,
);
$a[2] = &$a; //Элемент 2 ссылаем на массив
unset($a); //Удаляем переменную
if($i % 2500 == 0)
{
echo '<span style="color: #999;">Шаг '.$i.'.</span> Использовано памяти: '.ceil((memory_get_usage() - $current_memory_usage)/1024)." Кб<br>";
}
}
Как мы видим, сначала память увеличивается, а затем резко уменьшается. И так несколько раз
Шаг 0. Использовано памяти: 1 Кб
Шаг 2500. Использовано памяти: 978 Кб
Шаг 5000. Использовано памяти: 1954 Кб
Шаг 7500. Использовано памяти: 2931 Кб
Шаг 10000. Использовано памяти: 1 Кб
Шаг 12500. Использовано памяти: 978 Кб
Шаг 15000. Использовано памяти: 1954 Кб
Шаг 17500. Использовано памяти: 2931 Кб
Шаг 20000. Использовано памяти: 1 Кб
Шаг 22500. Использовано памяти: 978 Кб
Шаг 25000. Использовано памяти: 1954 Кб
Шаг 27500. Использовано памяти: 2931 Кб
Шаг 30000. Использовано памяти: 1 Кб
Шаг 2500. Использовано памяти: 978 Кб
Шаг 5000. Использовано памяти: 1954 Кб
Шаг 7500. Использовано памяти: 2931 Кб
Шаг 10000. Использовано памяти: 1 Кб
Шаг 12500. Использовано памяти: 978 Кб
Шаг 15000. Использовано памяти: 1954 Кб
Шаг 17500. Использовано памяти: 2931 Кб
Шаг 20000. Использовано памяти: 1 Кб
Шаг 22500. Использовано памяти: 978 Кб
Шаг 25000. Использовано памяти: 1954 Кб
Шаг 27500. Использовано памяти: 2931 Кб
Шаг 30000. Использовано памяти: 1 Кб
Такое поведение памяти происходит из-за особенности работы сборки циклических ссылок. Когда счётчик ссылок refcount__gc уменьшается (при разрыве связи со ссылкой и в случае удаления переменной), сборщик помечает потенциальную циклическую структуру zvav специальным буферным флагом, что означает нахождение структуры в корневом буфере. В терминологии алгоритма сборки содержимое буфера называется корнями. По факту наполнения буфера корнями в количестве 10 000 штук запускается сборщик мусора. Объём буфера настраивается в исходных кодах PHP в константе GC_ROOT_BUFFER_MAX_ENTRIES.
Обходя буфер, сборщик проверяет является ли корень мусорным или это живое использующееся значение. Проверка осуществляется по алгоритму, описанному в докладе «Concurrent Cycle Collection in Reference Counted Systems», представленный в 2001 году на Европейской конференции по объектно-ориентированному программированию.
Функции управления сборкой циклических ссылок
- gc_enable — включение сборки (документация);
- gc_disable — отключение сборки (документация);
- gc_collect_cycles — принудительное выполнение сборки (документация).