Сборщик мусора в PHP 5

18 марта 2008

Сегодня на работе разбирался с PHPшным сборщиком мусора. Обнаружилась одна жутко неприятная вещь, которая называется recursive reference memory leak – объекты с перекрестными ссылками не удаляются из памяти.

Как известно, объекты PHP 5 реализуются через механизм smart-pointer. Понимание этого механизма вообще нужно для адекватной работы с объектами, так что изложу вкратце. Осторожно, мнение выходца из C++. :)

Что такое smart-pointer? Это такой паттерн, который подсчитывает количество ссылок на себя, и когда оно становится равным нулю, удаляет объект, на который ссылается сам.

Подробнее про самостоятельную реализацию smart-pointer на PHP можно почитать, например, в Advanced PHP Programming.

  • Когда создается объект, под него выделяется участок память и smart-pointer. Переменной присваивается ссылка на smart-pointer.
  • При копировании объекта, на самом деле, создается еще одна ссылка на smart-pointer.
  • При клонировании объекта оператором clone создается копия памяти и новый smart-pointer с одной ссылкой (той, что новая).
  • При выполнении unset($object) удаляется только данная ссылка на объект – если это не последняя ссылка, то объект остается в памяти. (важно!)
  • При выполнении $object->__destruct() выполняется деструктор объекта как метод – это тоже не освобождает память! Так же, как и object.~Class() в C++. Более того, это, возможно, испортит объект – а ссылки на него могли остаться.
  • Единственный способ удалить объект из памяти – удалить все ссылки на него.

Конечно, обычно PHP очищает ссылки при выходе из области определения переменной, и память освобождается вовремя.

Но есть одна ситуация, и довольно часто используемая, когда этого не происходит.

<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
}

while (true) {
    $a = new A();
}
?>

Удаление свойства $a->b происходит после удаления объекта $a (резонно – в деструкторе оно может пригодиться). В деструкторе B объект $b->parent уже удален, и эта ссылка не очищается. Отсюда утечка памяти.

Пример из жизни

Привожу пример из жизни классической ActiveRecord-модели:

class Record{
 ...
 private $_fields;
 ...
}

class Field {
 ...
 private $_record;
 ...
}
...
while ($record = $result->next()) {
 $record->doSomething();
}

Так вот такой код со страшной силой жрет ОЗУ.

Решение

На данный момент приходится обнулять ссылки вручную:

<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
    function __destruct () {
        $this->b->__destruct();
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
    function __destruct () {
        unset($this->parent);
    }
}
?>

[edit] кроме того, вызывать деструктор для A придется вручную – поскольку в ссылка на него остается во вложенном объекте, он не будет удален автоматически.

Багрепорту, кстати, уже два с половиной года: http://bugs.php.net/bug.php?id=33595, и его вроде как закрыли в PHP 5.3.

Одно это делает переход на 5.3 весьма ожидаемым.



Семь комментариев. Напиши еще один
  1. Dd1510cd1a05294489764d5990fa854c # 19 марта 2008 Сергей написал:

    Вы забыли сказать, что деструктор А прийдется вызывать вручную:
    while (true) {

    $a = new A();
    $a-&gt;__destruct();
    

    }

  2. 777894ea5153122bfa6b83f5bbf23622 # 19 марта 2008 Леонид Шевцов (автор) написал:

    Спасибо! И правда, автоматически деструктор не вызывается.

  3. E63b695319c7aa5adfe3a6e5e251499b # 18 июня 2008 Snowcore (snowcore.net) написал:

    Спасибо за такой обзор, мне кажется в интернете слишком мало таких статей (особенно о PHP)

  4. D5c8c29dcecdb377a2affaf7da023daf # 30 июня 2008 standov (web.zavtra.com.ua) написал:

    Да в 5.3 появится гарбадж-коллектор что вместе с неймспейсами делает этот релиз таким долгожданным.

  5. Aa1d17193fb4252f41c429d8f7e7d416 # 20 декабря 2008 akalom написал:

    А кто сказал, что __destruct() не вызывается автоматически. По-моему он срабатывает в таком варианте unset($a);

  6. 6e18afc304a2e01b77c7f7c28daba284 # 30 октября 2009 Sectoid написал:

    Что люди только не сделают, чтобы нормальный язык с нормальным GC не использовать%)

    1. 777894ea5153122bfa6b83f5bbf23622 # 30 октября 2009 Леонид Шевцов (автор) написал:

      А нормальный язык – это какой?

(нужна разметка?)

  • **жирный**
  • > цитата

отменить