Утечки памяти обычно не беспокоят PHP-разработчиков. Типичное приложение обрабатывает один запрос и работает не больше секунды. После этого вся использованная им память освобождается. Даже если приложение кушает слишком много, максимум, разработчик упирается в memory_limit
, выставленный хостером, что решить в общем случае довольно просто: как только переменная становится не нужна, очищаем память, занимаемую ей, при помощи unset
.
Однако, при выполнении ресурсоёмких задач (например, обработки большого количества данных) или запуске PHP как демона проблема утечек встаёт очень остро.
В PHP 5.2 нет полноценного сборщика мусора. Вместо него используется подсчёт ссылок.
Все значения переменных хранятся в памяти. И чтобы занимать как можно меньше места, переменные с одинаковыми значениями просто ссылаются на одну и ту же область памяти. При этом количество ссылок подсчитывается и, как только оно становится равно нулю, память освобождается.
$a = 10; // выделяем область в памяти, одна ссылка $b = $a; // две ссылки unset($a); // одна ссылка
$a = 10; // выделяем область в памяти, одна ссылка $b = $a; // две ссылки $b = 1; // выделяем вторую область в памяти под значение 1, одна ссылка на 1, одна ссылка на 10
В PHP 5.2 причиной утечек являются циклические ссылки:
class A { private $b; function __construct(){ $this->b = new B($this); } } class B { private $a; function __construct($a){ $this->a = $a; } } $i=1; while($i<=1000){ $a = new A(); // 1 ссылка на A ($a). // 1 ссылка на B (A::$b). // 2 ссылки на A (B::$a). unset($a); // 1 ссылка на A всё ещё осталась. Память освобождать рановато. echo $i."\t".memory_get_usage()."\n"; $i++; }
Исправляется это явным уничтожением ссылки на B при помощи unset:
class A { private $b; function __construct(){ $this->b = new B($this); } function __destruct(){ unset($this->b); } } class B { private $a; function __construct($a){ $this->a = $a; } } $i=1; while($i<=1000){ $a = new A(); // 1 ссылка на A ($a). // 1 ссылка на B (A::$b). // 2 ссылки на A (B::$a). unset($a); // 1 ссылка на A (минус одна в B::__destruct). // 0 ссылок на A (unset). Память можно освободить. echo $i."\t".memory_get_usage()."\n"; $i++; }
В PHP 5.3 более умный сборщик мусора, который умеет находить и подчищать последствия использования циклических ссылок. Однако, поиск таких ссылок занимает значительное время и зависит от количества «неподчищенных» ссылок. Плюс к этому работает сборщик не постоянно, а срабатывает только при наполнении буфера ссылок. То есть до его срабатывания какое-то количество памяти всё-таки успевает утекать.
На заметку. Посмотреть, сколько памяти кушает ваше приложение можно при помощи следующих функций:
memory_get_usage()
— использованная скриптом память в байтах в момент вызова функции.memory_get_usage(true)
— использованная скриптом и менеджером памяти PHP память в байтах в момент вызова функции.memory_get_peak_usage()
— максимальное количество памяти в байтах, использованной скриптом с запуска скрипта до момента вызова функции.memory_get_peak_usage(true)
— максимальное количество памяти в байтах, использованной скриптом и менеджером памяти PHP с запуска скрипта до момента вызова функции.