Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 222 · От переменной → к worker

Память PHP: ссылки, циклический сборщик и долгие worker

Полная ветка Garbage Collection: сначала видимый жизненный цикл объекта, затем настоящий цикл A ↔ B и измеряемая политика долгого процесса.

Почему PHP обычно освобождает память сам и где появляется цикл

PHP освобождает большинство значений автоматически. Простое значение исчезает, когда на него больше никто не ссылается; дополнительный сборщик нужен для циклов, где объекты или массивы указывают друг на друга. Поэтому gc_collect_cycles() — не команда «очистить всю память», а точечный инструмент для потенциальных циклических остатков.

Два разных механизма
  1. Вход: переменная, объект и ссылки между объектами
  2. Действие: счётчик ссылок освобождает обычные значения; cycle collector периодически исследует возможные циклы
  3. Результат: память возвращается allocator-у PHP, а процесс не обязан немедленно вернуть её ОС
СитуацияЧто обычно происходит
$copy = $object; unset($object);Объект живёт, пока на него указывает $copy.
Последняя ссылка исчезлаОбъект уничтожается сразу обычным подсчётом ссылок.
A → B и B → A, внешние ссылки исчезлиСчётчики внутри цикла не равны нулю; цикл становится кандидатом GC.
Короткий web requestЗавершение запроса освобождает request memory; ручной GC редко нужен.
Worker обрабатывает тысячи jobsЦиклы и кэши накапливаются между заданиями; измерение и ограничение batch становятся важными.

Счётчик ссылок: объект живёт до последнего владельца

Имя переменной не содержит объект «внутри себя»: оно ведёт к значению. Присваивание объекта другой переменной создаёт ещё одну ссылку на тот же объект, а не независимую копию. unset() удаляет одно имя; destructor вызывается, когда исчезает последняя ссылка или завершается запрос.

Две переменные указывают на один объектСамостоятельный пример
<?php
declare(strict_types=1);

final class Parcel
{
    public function __construct(public string $code) {}
    public function __destruct() { echo "Освобождён {$this->code}\n"; }
}

$first = new Parcel('A-17');
$alias = $first;
unset($first);
echo "После unset(first) объект доступен: {$alias->code}\n";
unset($alias);
echo "Последняя ссылка удалена\n";
Ожидаемый результат
После unset(first) объект доступен: A-17
Освобождён A-17
Последняя ссылка удалена

Видимый порядок доказывает границу: первый unset не уничтожил объект, второй удалил последнюю ссылку. Для отдельного объекта нужен clone, а его вложенные объекты по умолчанию остаются общими, пока __clone() не изменит это явно.

  • Ссылки PHP с & и внутренний refcount — связанные, но не тождественные понятия.
  • Объект присваивается как handle к одному экземпляру; массив использует copy-on-write и копируется при изменении.
  • debug_zval_dump() полезен только как диагностика: временные аргументы и оптимизации влияют на показываемый refcount.

Цикл A ↔ B: почему одного refcount недостаточно

После unset внешних имён уже нет, но каждый Node всё ещё удерживает другой. Сборщик хранит потенциальные корни в специальном буфере и, когда запускается, определяет, достижимы ли они извне. gc_enable() и gc_disable() управляют автоматическим запуском; явный gc_collect_cycles() всё равно можно вызвать и при отключённом автоматическом GC.

Создать настоящий цикл A ↔ B и увидеть число собранных объектовСамостоятельный пример
<?php
declare(strict_types=1);

gc_disable();

final class Node
{
    public ?Node $next = null;
}

$first = new Node();
$second = new Node();
$first->next = $second;
$second->next = $first;
unset($first, $second);

echo 'Собрано в первый раз: ', gc_collect_cycles(), "\n";
echo 'Собрано во второй раз: ', gc_collect_cycles(), "\n";
Ожидаемый результат
Собрано в первый раз: 2
Собрано во второй раз: 0

Первый вызов освобождает два недостижимых объекта. Второй показывает ноль: «собрать ещё раз» не освобождает обычные живые значения и не является универсальным способом снизить RSS.

Как читать цикл
  1. Скрипт создаёт A и B
  2. A.next указывает на B
  3. B.next указывает на A
  4. unset удаляет только внешние имена
  5. cycle collector доказывает недостижимость пары и освобождает её

Функция gc_status() показывает статистику текущего процесса: поля и их точный набор зависят от версии PHP. Для диагностики фиксируйте версию, gc_enabled(), число runs/collected и память до/после одного и того же workload; не сравнивайте разные процессы только по одному RSS.

Производительность GC: измерить batch, циклы и реальную границу worker

Сборка циклов имеет цену: PHP хранит кандидатов и выполняет обход. Отключение GC иногда ускоряет короткий участок без циклов, но в долгом worker способно вызвать рост памяти. Сначала измеряйте реальный workload. memory_get_usage(true) включает память, зарезервированную allocator-ом; memory_get_peak_usage(true) — исторический максимум, который не обязан уменьшаться после unset или GC.

Измерять ограниченный batch, а не ждать аварии workerСамостоятельный пример
<?php
declare(strict_types=1);

function processBatch(int $jobs, int $payloadBytes): array
{
    $peakBefore = memory_get_peak_usage(true);
    for ($i = 0; $i < $jobs; $i++) {
        $payload = str_repeat('x', $payloadBytes);
        $checksum = hash('sha256', $payload);
        unset($payload, $checksum);
    }
    return [
        'jobs' => $jobs,
        'peak_grew' => memory_get_peak_usage(true) >= $peakBefore,
        'gc_enabled' => gc_enabled(),
    ];
}

$report = processBatch(3, 1024);
echo 'Заданий: ', $report['jobs'], "\n";
echo 'Пик не уменьшился: ', $report['peak_grew'] ? 'да' : 'нет', "\n";
echo 'GC включён: ', $report['gc_enabled'] ? 'да' : 'нет', "\n";
Ожидаемый результат
Заданий: 3
Пик не уменьшился: да
GC включён: да

Опыт детерминированно показывает семантику peak: максимум не идёт назад. Для production журналируют usage, peak, gc_status, длительность и число jobs, затем перезапускают worker по обоснованному лимиту.

ПриёмКогда разуменРиск
Оставить автоматический GCОбычное приложение и неизвестная структура объектов.Минимальный; это безопасная отправная точка.
gc_collect_cycles() между batchИзмерено накопление циклов после законченной порции.Слишком частый вызов тратит CPU без пользы.
Временно gc_disable()Короткий доказанный участок создаёт много кандидатов, но не циклы.При исключении или новом цикле память растёт; включать обратно в finally.
Перезапуск worker после N jobs/MBНужна предсказуемая граница и есть supervisor.Не заменяет поиск утечки и идемпотентность задания.
Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.