Одним из самых больших преимуществ использования языка сценариев является то, что вы можете воспользоваться его механизмом автоматической сборки мусора (освобождение памяти). Вам не нужно выполнять какую-либо обработку для освобождения памяти после использования переменной, PHP сделает это за вас.
Конечно, мы можем вызвать функцию unset(), чтобы освободить память, если захотим, но обычно в этом нет необходимости.
Однако в PHP существует по крайней мере одна ситуация, когда память не будет освобождена автоматически, даже если unset() вызывается вручную. Подробности можно найти по адресу: http://bugs.php.net/bug.php?id=33595 .
Симптомы проблемы: если между двумя объектами существует взаимосвязь, например «родительский объект-дочерний объект», вызов unset() для родительского объекта не освободит память, ссылающуюся на родительский объект в дочернем объекте (даже если родительский объект объект собран мусором, тоже не работает).
Немного запутался? Давайте посмотрим на следующий код:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}}class Bar {function __construct($foo = null){$this-> foo = $foo;}} while (true) {$foo = new Foo();unset($foo);echo number_format(memory_get_usage()) . "n";}?>Запустите этот код, и вы увидите использование памяти. становится все выше и выше, пока не израсходуется.
...33,551,61633,551,97633,552,33633,552,696PHP Неустранимая ошибка: разрешенный размер памяти 33554432 байт исчерпан (пытался выделить 16 байт) в memleak.php в строке 17. Для большинства программистов PHP это такая ситуация. это не проблема.
Но если вы используете много объектов, которые ссылаются друг на друга в долго выполняющемся коде, особенно если объекты относительно большие, память будет быстро исчерпана.
Решение Userland несколько утомительно и неэлегантно, но ссылка на bugs.php.net, упомянутая ранее, предоставляет решение.
Это решение использует метод деструктора перед освобождением объекта для достижения этой цели. Метод Destructor может очистить все ссылки на внутренние родительские объекты, а это означает, что эта часть памяти, которая в противном случае могла бы переполниться, может быть освобождена.
Вот код «после исправления»:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}function __destruct(){unset($this->bar);}}class Bar {function __construct($foo = null){$this->foo = $foo;}} while (true) {$foo = new Foo();$foo->__destruct();unset($foo);echo number_format(memory_get_usage()) . "n";}?>Обратите внимание на новый метод Foo::__destruct() и вызов $foo->__destruct() перед освобождением объекта. Теперь этот код решает проблему увеличения использования памяти, поэтому код может работать хорошо.
Решение ядра PHP?
Почему происходит переполнение памяти? Я не разбираюсь в исследованиях ядра PHP, но уверен, что эта проблема связана с подсчетом ссылок.
Счетчик ссылок $foo, указанный в $bar, не будет уменьшаться, поскольку родительский объект $foo освобожден. В это время PHP считает, что вам все еще нужен объект $foo, поэтому эта часть памяти не будет освобождена. или около того.
Здесь действительно проявляется мое невежество, но общая идея такова: счетчик ссылок не уменьшается, поэтому часть памяти никогда не освобождается.
По вышеупомянутой ссылке на bugs.php.net я увидел, что изменение процесса сборки мусора приведет к значительному снижению производительности, и, поскольку я мало что знаю о подсчете ссылок, я предположил, что это правда.
Вместо изменения процесса сборки мусора, почему бы не использовать функцию unset() для освобождения внутренних объектов? (Или вызвать __destruct() при освобождении объекта?)
Возможно, разработчики ядра PHP смогут внести изменения в этот механизм сборки мусора здесь или где-нибудь еще.
Обновление: Мартин Фьордвальд упомянул в комментариях патч, написанный Дэвидом Вангом для сбора мусора (на самом деле он больше похож на «целый кусок ткани» - очень огромный. Подробности см. в информации об экспорте CVS в конце этого письма.) Действительно. существует (по электронной почте) и привлек внимание членов сообщества разработчиков ядра PHP. Вопрос в том, стоит ли ставить этот патч в PHP5.3, а он не получил особой поддержки. Я думаю, что хорошим компромиссом является вызов метода __destruct() в объекте функции unset();