#7. 6 php Алгоритм сбора мусора. Циклические ссылки

Алгоритм сбора мусора

Для того, чтобы определить в какой момент нужно вызывать деструктор объекта, каждый объект содержит скрытое поле, хранящее счетчик ссылок. Каждый раз, когда в программе появляется новая ссылка на объект, это значение счетчика увеличивается на единицу, а при удалении ссылки — уменьшается на единицу. Если в какой-то момент обнаружено, что значение счетчика обнулилось, объект удаляется.

При удалении объекта происходит следующее:

  • удаляются все ссылки, которые содержит сам этот объект. Если в процессе этой процедуры какой-либо подчиненный объект теряет последнюю ссылку, он также будет удален и т.д.;
  • вызывается деструктор объекта;
  • освобождается занимаемая память.

Например, пусть вместе с именем животного в объекте класса Animal мы будем хранить также список его детей. Для этого в примере 1 добавлено свойство children и метод add_child в описание класса.

Пример 1. Удаление объекта, содержащего ссылки на другие объекты

<HTML>
    <HEAD>
        <TITLE>
            Удаление объекта, содержащего ссылки на другие объекты
        </TITLE>
    </HEAD>
    <BODY>
        <CENTER>
            <H1>
                Удаление объекта, содержащего ссылки на другие объекты.
            </H1>
            <?php
                class Animal
                {
                    private $name;
                    private $children;

                    public function __construct($text)
                    {
                        $this->name = $text;
                        $this->children = [];
                    }

                    public function set_name($text)
                    {
                        $this->name = $text;
                    }

                    public function get_name()
                    {
                        return $this->name;
                    }

                    public function add_child($child)
                    {
                        $this->children[] = $child;
                    }

                    public function __destruct()
                    {
                        echo "Объект ", $this->name, " уничтожен.<BR>";
                    }
                }

                $lion = new Animal("Бонифаций");
                $lion->add_child(new Animal("Симба"));
                $lion = null;
                echo "Конец программы.<BR>";
            ?>
        </CENTER>
    </BODY>
</HTML>

Итак, мы присвоили переменной $lion ссылку на объект с именем "Бонифаций", затем с помощью метода add_child добавили ему одного ребенка — объект "Симба", и после этого обнулили переменную $lion.

Результат выполнения примера:



Рис. 3 Удаление объекта, имеющего ссылки на другие объекты

Мы видим, что при обнулении единственной ссылки на объект "Бонифаций" сначала вызывается деструктор самого этого объекта, а затем и деструктор подчиненного объекта "Симба".

Проблема циклических ссылок

возникает, когда два объекта прямо или косвенно ссылаются друг на друга. В примере 2 мы создаем два объекта класса Animal — "Бонифаций" и "Симба":

$father = new Animal("Бонифаций");
$child = new Animal("Симба");

Затем при помощи функции add_child сохраняем в свойстве children объекта "Бонифаций" ссылку на объект "Симба":

$father->add_child($child);

А при помощи функции set_father сохраняем в свойстве father объекта "Симба" ссылку на объект "Бонифаций":

$child->set_father($father);

И наконец, перезапишем переменные, ссылающиеся на объекты:

$father = $child = null;

Пример 2. Циклические ссылки

<HTML>
    <HEAD>
        <TITLE>
            Циклические ссылки
        </TITLE>
    </HEAD>
    <BODY>
        <CENTER>
            <H1>
                Циклические ссылки.
            </H1>
            <?php
                class Animal
                {
                    private $name;
                    private $children;
                    private $father;

                    public function __construct($text)
                    {
                        $this->name = $text;
                        $this->children = [];
                    }

                    public function set_name($text)
                    {
                        $this->name = $text;
                    }

                    public function get_name()
                    {
                        return $this->name;
                    }

                    public function add_child($child)
                    {
                        $this->children[] = $child;
                    }
                    
                    public function set_father($father)
                    {
                        $this->father = $father;
                    }
                            

                    public function __destruct()
                    {
                        echo "Объект ", $this->name, " уничтожен.<BR>";
                    }
                }

                $father = new Animal("Бонифаций");
                $child = new Animal("Симба");
                     
                
                $father->add_child($child);
                $child->set_father($father);
                
                $father = $child = null;
                
                echo "Конец программы.<BR>";
            ?>
        </CENTER>
    </BODY>
</HTML> 

Запустив пример, мы увидим, что оба объекта будут разрушены уже после завершения работы скрипта (рис. 2). Несмотря на то, что после строки $father = $child = null в программе отсутствует доступ к ним, память, занимаемая объектами, не освобождается. Это происходит как раз по причине циклических ссылок. Объекты "Бонифаций" и "Симба" ссылаются друг на друга, поэтому даже при удалении всех остальных ссылок, счетчик каждого из них равен 1.



Рис. 2. Циклические ссылки

В данном примере "длина цикла" равна двум. Однако та же самая ситуация возникает и в более сложных случаях, когда "А" ссылается на "B", "B" ссылается на "С", "C" ссылается на "А" и т.п. Если в программе есть циклические ссылки, алгоритм сбора мусора, описанный выше, не срабатывает.

Начиная с PHP 5.3, в сборщик мусора PHP внедрен синхронный механизм сбора циклических ссылок. Все объекты, генерирующие ссылки, помещаются в специальный буфер, размер которого составляет 10000. При заполнении буфера стартует процедура сборки мусора, в результате которой алгоритм корректирует счетчики. Объекты, чьи счетчики стали равны нулю, удаляются. По умолчанию сборщик мусора включен. Если скрипт работает короткое время и потребляет мало памяти, можно увеличить производительность за счет отключения сборщика мусора, установив значение директивы zend.enable_gc в конфигурационном файле php.ini в значение off.

mariyas's picture

добавлено: циклические ссылки