Ответ
Утечка памяти — это ситуация, когда приложение постоянно выделяет память, но не освобождает её, даже когда объекты больше не нужны. Со временем это может исчерпать доступную память и привести к падению скрипта или сервера.
Хотя PHP имеет встроенный сборщик мусора (Garbage Collector, GC), утечки возможны в следующих сценариях:
- Циклические ссылки на объектах или массивах: Когда два или более объекта ссылаются друг на друга, создавая изолированный "остров", на который нет внешних ссылок. Старый GC (до PHP 5.3) не мог их очистить. Современный GC (алгоритм подсчета ссылок с циклическим сборщиком) справляется с этим, но не всегда мгновенно.
- Глобальные переменные и статические свойства/массивы: Данные, хранящиеся в них, живут до конца выполнения скрипта (для глобальных) или между вызовами (для статических в функциях/методах). Если постоянно добавлять туда данные без очистки — память будет расти.
- Расширения PHP: Потенциальные утечки могут быть в некорректно написанных сторонних расширениях на C.
Пример потенциальной проблемы (циклическая ссылка в долгоживущем скрипте):
class Service {
private array $listeners = [];
public function addListener(object $listener): void {
$this->listeners[] = $listener;
// Если $listener хранит ссылку на этот Service -> циклическая ссылка
}
}
// В долгоживущем процессе (например, worker ReactPHP или RoadRunner)
$service = new Service();
while (true) {
$listener = new class($service) {
private Service $svc;
public function __construct(Service $svc) {
$this->svc = $svc; // Создается циклическая ссылка
}
};
$service->addListener($listener);
// После итерации $listener не уничтожается, т.к. на него ссылается $service->listeners
// А на $service ссылается $listener->svc
usleep(1000);
}
// Память будет постепенно расти.
Решение: Для разрыва циклических ссылок можно использовать WeakReference (PHP 7.4+) или явно удалять ссылки (например, $listener->svc = null;). Для глобальных/статических кэшей стоит устанавливать лимиты и использовать TTL.
Ответ 18+ 🔞
Да ты послушай, что за дичь бывает! Утечка памяти — это когда твоё приложение как запойный алкаш: хавает оперативку пачками, а обратно не отдаёт ни грамма. Объекты уже давно сдохли, а память за ними как будто зарезервирована навечно. В итоге — овердохуища гигабайтов в трубу, скрипт хрипит и накрывается медным тазом, а сервер тупо ложится.
PHP-то, вроде, умный, у него есть встроенный дворник — сборщик мусора (Garbage Collector). Но он, блядь, не всесильный, как нам хочется. Утекать может в трёх основных случаях, ёпта:
- Циклические ссылки между объектами или массивами. Представь: два чувака крепко обнялись и друг на друга смотрят. Со стороны никто к ним не обращается, но они друг друга держат, чтобы не упасть. Старый GC (до PHP 5.3) на такое смотрел и просто разводил руками — «хуй с горы». Современный, конечно, поумнее, он рано или поздно разберётся, но не факт, что мгновенно. А если такие обнимашки создаются тысячами в секунду — пиши пропало.
- Глобальные переменные и статические свойства. Ну это же классика! Скинул туда данные — и они живут там вечно, как семейная реликвия в чулане. В обычном скрипте он кончится и всё почистит. А вот если у тебя воркер какой-нибудь долгоживущий, типа в ReactPHP, так он будет эту свалку годами копить, пока не лопнет. Доверия к такому коду — ноль ебать.
- Кривые расширения на C. Вот тут вообще тёмный лес. Если в каком-нибудь стороннем модуле программист накосячил — память может утекать в никуда, и с PHP-кода ты это нихуя не отследишь.
Вот, смотри, наглядный пиздец (цикличка в вечном воркере):
class Service {
private array $listeners = [];
public function addListener(object $listener): void {
$this->listeners[] = $listener;
// Если $listener тащит в себе ссылку на этот же Service — всё, пизда рулю, циклическая ссылка готова.
}
}
// Допустим, это воркер в RoadRunner, который не умирает.
$service = new Service();
while (true) {
$listener = new class($service) {
private Service $svc;
public function __construct(Service $svc) {
$this->svc = $svc; // Ёб твою мать! Вот она, родная, циклическая ссылка!
}
};
$service->addListener($listener);
// Итерация прошла, а $listener не сдох — он же в массиве $service->listeners.
// А сам $service болтается в свойстве $listener->svc. Взаимный hostage situation, блядь.
usleep(1000);
}
// Память будет тихо и неотвратимо ползти вверх, как жопа программиста после 30.
Что делать, ёклмн? Чтобы разорвать эти ебушки-воробушки, можно использовать WeakReference (если ты на PHP 7.4+). Это такая хитрая жопа — ссылка, которая не мешает сборщику мусора удалить объект. Либо явно обнулять связи, когда они уже не нужны: $listener->svc = null;. А глобальные кэши — так тебе вообще строгий лимит по размеру и TTL (время жизни) ставить, а то разжиреешь как тот самый Фарлаф.
Видео-ответы
▶
▶
▶
▶