Как работает сборщик мусора (Garbage Collector) в PHP?

«Как работает сборщик мусора (Garbage Collector) в PHP?» — вопрос из категории PHP Core, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Сборщик мусора (Garbage Collector, GC) в PHP использует комбинированную модель: основной механизм — подсчёт ссылок, а для обработки циклических ссылок — дополнительный циклический сборщик.

1. Подсчёт ссылок (Reference Counting) Каждая переменная (zval структура в ядре PHP) хранит счётчик ссылок (refcount). Когда переменная создаётся или на неё начинают ссылаться, счётчик увеличивается. Когда ссылка уничтожается (например, переменная выходит из области видимости), счётчик уменьшается.

$a = new stdClass(); // zval для объекта: refcount = 1
$b = $a;             // Та же zval, refcount = 2
unset($a);           // refcount = 1
unset($b);           // refcount = 0 -> память освобождается НЕМЕДЛЕННО

2. Циклический сборщик мусора (Cycle Collector) Проблема возникает, когда объекты ссылаются друг на друга, образуя цикл, но внешних ссылок на этот цикл нет. Механизм подсчёта ссылок не может обнаружить такие «мусорные» циклы.

class Node {
    public $next;
}

$a = new Node();
$b = new Node();
$a->next = $b; // $a ссылается на $b
$b->next = $a; // $b ссылается на $a -> ЦИКЛ

unset($a, $b); // Внешние ссылки уничтожены, но refcount каждого объекта = 1
// Память не освобождена подсчётом ссылок.

Для этого в PHP (с версии 5.3) есть циклический сборщик. Он периодически запускается (или при вызове gc_collect_cycles()) и находит такие изолированные циклы, уменьшая их внутренние счётчики ссылок до нуля, что приводит к освобождению памяти.

Практические аспекты:

  • Не гарантирует мгновенную очистку: Память от циклических ссылок освобождается не сразу, а при запуске циклического GC.
  • Влияние на производительность: Запуск циклического GC требует ресурсов. В высоконагруженных скриптах его можно отключить gc_disable(), но это риск утечек памяти.
  • Только для объектов: GC работает с переменными, являющимися объектами (zval типа object). Ресурсы (например, дескрипторы файлов или соединения с БД) должны освобождаться вручную (например, fclose(), unset($pdo)).