Управление памятью PHP для администраторов

Раздел: Администрирование PHP -> Настройка PHP

Управление памятью в PHP - важная задача администрирования веб-сервера. Неправильные настройки приводят к ошибкам превышения лимита и снижению производительности. Далее рассматриваются основные варианты решения задач выделения памяти.

Основные подходы к управлению памятью в PHP

Наиболее эффективным решением является изменение директивы memory_limit в конфигурации PHP. Это ограничение задается в байтах или в сокращённом виде (например, 256M, 1G). Изменить значение можно разными способами:

  • В файле php.ini (глобально для всего сервера или пула).
  • В файле .user.ini (для каталога).
  • С помощью функции ini_set() внутри скрипта (работает только для скрипта, если не запрещено хостингом).

Пример установки лимита в 256 мегабайт внутри скрипта:

<?php
ini_set('memory_limit', '256M');
echo 'Текущий лимит: ' . ini_get('memory_limit');
?>

Php выделить память (выделение памяти php)

Результат: Текущий лимит: 256M. Если хостинг запрещает переопределение, вызов ini_set() вернет false или вызовет предупреждение.

Цели использования: увеличение доступной памяти для скриптов, обработка больших файлов, сложные вычисления. Случаи: импорт/экспорт данных, генерация отчётов, работа с изображениями высокого разрешения.

Как освободить память после обработки больших переменных?

Использование функции unset() для удаления переменных. Однако сразу после вызова unset() память может не вернуться в систему - сборщик мусора освободит её позже. Если переменная содержит циклические ссылки, требуется принудительная сборка.

<?php
$large = str_repeat('x', 10 * 1024 * 1024); // 10 MB
echo memory_get_usage(true) . "\n"; // ~10 MB
unset($large);
echo memory_get_usage(true) . "\n"; // может быть столько же
gc_collect_cycles();
echo memory_get_usage(true) . "\n"; // память освобождена
?>

Php ini timezone (настройка часового пояса в php (date.timezone))

Проблема: если на переменную есть другие ссылки, unset() лишь удаляет ссылку, но не данные. Решение: отслеживать количество ссылок с помощью debug_zval_refs() или осознанно обнулять все ссылки.

Типичная ошибка: предположение, что unset($array) мгновенно освобождает память в системном пуле. На самом деле PHP может не возвращать память ОС, а держать в собственном пуле.

Как принудительно запустить сборщик мусора для освобождения циклических ссылок?

Включить сборщик мусора директивой zend.enable_gc = 1 в php.ini или функцией gc_enable(). Циклические ссылки возникают при взаимных ссылках объектов. Принудительный вызов gc_collect_cycles() освобождает память.

<?php
gc_enable();
class A { public $b; }
class B { public $a; }
$a = new A;
$b = new B;
$a->b = $b;
$b->a = $a;
unset($a, $b);
$freed = gc_collect_cycles();
echo "Освобождено объектов: $freed";
?>

Xampp php ini (настройка php.ini в xampp)

Проблема: сборщик мусора может не запускаться автоматически при каждом unset(). Если скрипт долго работает, количество циклических ссылок накапливается. Решение: периодический вызов gc_collect_cycles() или настройка вероятности запуска через gc_probability и gc_divisor в php.ini.

Ошибка: вызов gc_collect_cycles() внутри цикла с большим количеством объектов может снизить производительность. Рекомендуется вызывать его после пакетной обработки.

Как отследить потребление памяти в процессе выполнения?

Функции memory_get_usage() и memory_get_peak_usage() возвращают объём памяти, выделенной скрипту. Аргумент true показывает выделенную системой память, false - только занятую.

<?php
echo 'Текущее использование: ' . memory_get_usage() . ' байт';
echo 'Пиковое использование: ' . memory_get_peak_usage() . ' байт';
?>

Php ini sessions (настройка сессий в php (session.*))

Проблема: разница между memory_get_usage(true) и memory_get_usage(false) может вводить в заблуждение. Первое значение часто больше, так как включает выделенный пул. Решение: использовать memory_get_usage(true) для оценки предела, а memory_get_peak_usage(true) для пиковых значений.

Как уменьшить потребление памяти при обработке больших массивов?

Использование генераторов (yield) позволяет обрабатывать данные потоково, не загружая весь набор в массив. Также помогает чтение файлов построчно, а не целиком.

<?php
function getLines($file) {
    $handle = fopen($file, 'r');
    while (!feof($handle)) {
        yield fgets($handle);
    }
    fclose($handle);
}
foreach (getLines('bigfile.csv') as $line) {
    // обработка одной строки
}
?>

Проблема: генераторы не подходят для случайного доступа. Если нужна сортировка всего набора, потребуется загрузка в память. Решение: использовать внешние сортировки (через файлы) или базы данных.

Ошибка: забыть закрыть файл в генераторе - утечка дескриптора. Всегда нужно закрывать файл в блоке finally или после завершения генератора.

- изменить настройки php (изменение настроек php)
- Index php page info (страница phpinfo())
- Php ini memory (настройка memory_limit в php)

Расширенные примеры работы с памятью в PHP

Ниже приведены примеры, которые помогут глубже понять механизмы выделения и освобождения памяти.

Пример 1. Изменение memory_limit в разных SAPI. Настройки для CLI и FPM могут различаться. В php.ini можно задать разные значения для разных режимов с помощью [CLI] и [FPM] секций.

Пример
; /etc/php/8.3/cli/php.ini
memory_limit = 512M

; /etc/php/8.3/fpm/php.ini
memory_limit = 128M

Результат: в CLI скрипты получают 512 MB, веб-запросы - 128 MB. Для .user.ini файла директива memory_limit также поддерживается, если включено соответствующее разрешение в user_ini.filename.

Пример 2. Мониторинг пикового потребления. Скрипт, который выводит текущий и пиковый лимит в мегабайтах.

Пример
<?php
$usage = memory_get_usage(true) / 1024 / 1024;
$peak = memory_get_peak_usage(true) / 1024 / 1024;
echo sprintf('Текущее: %.2f MB, Пиковое: %.2f MB', $usage, $peak);
?>
Текущее: 0.34 MB, Пиковое: 0.45 MB

Пример 3. Обработка большого CSV-файла с помощью генератора. Файл размером 1 ГБ обрабатывается без загрузки в память.

Пример
<?php
function readCSV($filename) {
    $f = fopen($filename, 'r');
    try {
        while (($row = fgetcsv($f)) !== false) {
            yield $row;
        }
    } finally {
        fclose($f);
    }
}
$total = 0;
foreach (readCSV('/tmp/large.csv') as $row) {
    $total += (int)$row[2];
}
echo "Сумма: $total";
?>
Сумма: 123456789 (память при этом не превышает нескольких мегабайт)

Пример 4. Циклические ссылки и ручной сбор мусора.

Пример
<?php
gc_enable();
class Node {
    public $next;
    public $data;
    public function __construct($data) {
        $this->data = $data;
    }
}
$head = new Node('A');
$second = new Node('B');
$head->next = $second;
$second->next = $head; // циклическая ссылка
$start = memory_get_usage(true);
unset($head, $second);
echo 'После unset: ' . (memory_get_usage(true) - $start) . " байт\n";
$collected = gc_collect_cycles();
echo 'Собрано ссылок: ' . $collected . "\n";
echo 'После сборки: ' . (memory_get_usage(true) - $start) . " байт\n";
?>
После unset: 0 байт
Собрано ссылок: 4
После сборки: -4096 байт (память освобождена)

Пример 5. Обработка ошибки превышения memory_limit. Использование register_shutdown_function для перехвата фатальной ошибки.

Пример
<?php
register_shutdown_function(function() {
    $error = error_get_last();
    if ($error && $error['type'] === E_ERROR) {
        if (strpos($error['message'], 'Allowed memory size') !== false) {
            echo 'Ошибка: превышен лимит памяти. Увеличьте memory_limit.';
        }
    }
});
ini_set('memory_limit', '1M'); // создание ошибки
$data = str_repeat('x', 2 * 1024 * 1024); // 2 MB
?>
Ошибка: превышен лимит памяти. Увеличьте memory_limit.

Пример 6. Использование memory_limit в контейнере Docker. Настройка через переменную окружения PHP_MEMORY_LIMIT.

Пример
# Dockerfile
FROM php:8.3-fpm
ENV PHP_MEMORY_LIMIT=256M
RUN echo "memory_limit=${PHP_MEMORY_LIMIT}" >> /usr/local/etc/php/conf.d/custom.ini

Результат: при запуске контейнера лимит будет установлен в 256 MB.

Пример 7. Выделение памяти для временных переменных в цикле. Сравнение foreach с созданием копии массива.

Пример
<?php
$array = range(1, 100000);
echo 'До: ' . memory_get_usage(true) . "\n";
foreach ($array as $value) {
    // ничего не делаем, PHP создаёт внутреннюю копию?
}
echo 'После foreach: ' . memory_get_usage(true) . "\n";
// чтобы избежать копирования, передавать по ссылке (не рекомендуется)
foreach ($array as &$value) {}
unset($value);
echo 'После foreach с ссылкой: ' . memory_get_usage(true) . "\n";
?>
До: 4194304
После foreach: 4194304
После foreach с ссылкой: 4194304 (изменения минимальны в новых версиях PHP)

Выделение памяти PHP - comments

En
Php выделить память (php)