Я столкнулся с ужасным сообщением об ошибке, возможно, из-за кропотливых усилий PHP исчерпал память:
Допустимый объем памяти #### байт исчерпан (попытка выделить #### байт) в file.php в строке 123
Повышение лимита
Если вы знаете, что делаете, и хотите увеличить лимит, см. Memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Осторожно! Возможно, вы решаете только симптом, а не проблему!
Диагностика утечки:
Сообщение об ошибке указывает на строку внутри цикла, который, как я полагаю, вызывает утечку или ненужное накопление памяти. Я напечатал memory_get_usage()
операторы в конце каждой итерации и вижу, как число медленно растет, пока не достигнет предела:
foreach ($users as $user) {
$task = new Task;
$task->run($user);
unset($task); // Free the variable in an attempt to recover memory
print memory_get_usage(true); // increases over time
}
Для целей этого вопроса давайте предположим, что худший код спагетти, который только можно вообразить, прячется в глобальной области где-то в $user
или Task
.
Какие инструменты, уловки PHP или отладка voodoo могут помочь мне найти и исправить проблему?
источник
Ответы:
В PHP нет сборщика мусора. Он использует подсчет ссылок для управления памятью. Таким образом, наиболее частым источником утечек памяти являются циклические ссылки и глобальные переменные. Боюсь, что если вы используете фреймворк, вам придется перебирать много кода, чтобы найти его. Самый простой инструмент - это выборочно размещать вызовы
memory_get_usage
и сужать их до утечек кода. Вы также можете использовать xdebug для создания трассировки кода. Запустите код со следами выполнения иshow_mem_delta
.источник
Вот трюк, который мы использовали, чтобы определить, какие скрипты используют больше всего памяти на нашем сервере.
Сохраните следующий фрагмент в файле, например, по адресу
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Используйте его, добавив в httpd.conf следующее:
Затем проанализируйте файл журнала на
/var/log/httpd/php_memory_log
Это может потребоваться,
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
прежде чем ваш веб-пользователь сможет записывать в файл журнала.источник
Я заметил однажды в старом скрипте, что PHP будет поддерживать переменную «as» как в области видимости даже после моего цикла foreach. Например,
Я не уверен, исправят ли это будущие версии PHP или нет, с тех пор, как я это видел. Если это так, вы можете
unset($user)
послеdoSomething()
строки очистить ее из памяти. YMMV.источник
unset()
это сделать, но имейте в виду, что для объектов все, что вы делаете, это меняете место, на которое указывает ваша переменная - вы фактически не удалили ее из памяти. PHP в любом случае автоматически освободит память, как только она выйдет за рамки, поэтому лучшим решением (с точки зрения этого ответа, а не вопроса OP) является использование коротких функций, чтобы они не зависали от этой переменной из цикла. длинный.Есть несколько возможных точек утечки памяти в php:
Достаточно сложно найти и исправить первые 3 без глубокого реверс-инжиниринга или знания исходного кода php. Для последнего вы можете использовать двоичный поиск кода утечки памяти с помощью memory_get_usage
источник
Недавно я столкнулся с этой проблемой в приложении, при, как я полагаю, аналогичных обстоятельствах. Скрипт, который запускается в PHP cli и проходит множество итераций. Мой сценарий зависит от нескольких базовых библиотек. Я подозреваю, что причиной является конкретная библиотека, и я потратил несколько часов напрасно, пытаясь добавить соответствующие методы уничтожения в ее классы, но безрезультатно. Столкнувшись с длительным процессом преобразования в другую библиотеку (которая может иметь те же проблемы), я придумал грубый способ решения проблемы в моем случае.
В моей ситуации на linux cli я перебирал кучу пользовательских записей и для каждой из них создавал новый экземпляр нескольких созданных мною классов. Я решил попробовать создать новые экземпляры классов с помощью метода PHP exec, чтобы этот процесс выполнялся в «новом потоке». Вот действительно базовый пример того, о чем я говорю:
Очевидно, что у этого подхода есть ограничения, и нужно знать об опасностях этого, так как было бы легко создать кроликовую работу, однако в некоторых редких случаях это может помочь преодолеть трудное положение, пока не будет найдено лучшее решение. , как и в моем случае.
источник
Я столкнулся с той же проблемой, и моим решением было заменить foreach на обычный for. Я не уверен в специфике, но похоже, что foreach создает копию (или каким-то образом новую ссылку) на объект. Используя обычный цикл for, вы получаете прямой доступ к элементу.
источник
Я бы посоветовал вам проверить руководство по php или добавить
gc_enable()
функцию для сбора мусора ... То есть утечки памяти не влияют на работу вашего кода.PS: php имеет сборщик мусора,
gc_enable()
который не принимает аргументов.источник
Недавно я заметил, что лямбда-функции PHP 5.3 оставляют дополнительную память, используемую при их удалении.
Я не уверен, почему, но кажется, что каждая лямбда требует дополнительных 250 байт даже после удаления функции.
источник
Если то, что вы говорите о том, что PHP выполняет сборку мусора только после того, как функция является истинной, вы можете обернуть содержимое цикла внутри функции в качестве обходного пути / эксперимента.
источник
run()
вызываемое - это тоже функция, в конце которой должен произойти сборщик мусора.Одна огромная проблема, с которой я столкнулся, заключалась в использовании create_function . Как и в лямбда-функциях, он оставляет сгенерированное временное имя в памяти.
Другая причина утечки памяти (в случае Zend Framework) - это Zend_Db_Profiler. Убедитесь, что этот параметр отключен, если вы запускаете скрипты в Zend Framework. Например, в моем application.ini было следующее:
Выполнение примерно 25000 запросов + загрузка перед этим довели объем памяти до 128 МБ (мой максимальный предел памяти).
Просто установив:
Достаточно было не больше 20 Мб
И этот скрипт выполнялся в CLI, но он создавал экземпляр Zend_Application и запускал Bootstrap, поэтому он использовал конфигурацию «разработки».
Очень помог запустить скрипт с профилированием xDebug
источник
Я не видел, чтобы это явно упоминалось, но xdebug отлично справляется с профилированием времени и памяти ( начиная с версии 2.6 ). Вы можете взять информацию, которую он генерирует, и передать ее пользовательскому интерфейсу по вашему выбору: webgrind (только время), kcachegrind , qcachegrind или другие, и он генерирует очень полезные деревья вызовов и графики, чтобы вы могли найти источники ваших различных проблем. .
Пример (qcachegrind):
источник
Я немного опоздал к этому разговору, но поделюсь кое-чем, что имеет отношение к Zend Framework.
У меня возникла проблема с утечкой памяти после установки php 5.3.8 (с использованием phpfarm) для работы с приложением ZF, которое было разработано с помощью php 5.2.9. Я обнаружил, что утечка памяти была вызвана в файле Apache httpd.conf, в моем определении виртуального хоста, где говорится
SetEnv APPLICATION_ENV "development"
. После комментирования этой строки утечка памяти прекратилась. Я пытаюсь найти встроенный обходной путь в моем php-скрипте (в основном, определяя его вручную в основном файле index.php).источник
"development"
среде обычно есть множество журналов и профилей, которых может не быть в других средах. Комментирование строки заставило ваше приложение использовать вместо этого среду по умолчанию, которой обычно является"production"
или"prod"
. Утечка памяти все еще существует; код, который его содержит, просто не вызывается в этой среде.Я не видел, чтобы это упоминалось здесь, но одна вещь, которая может быть полезна, - это использование xdebug и xdebug_debug_zval ('variableName'), чтобы увидеть refcount.
Я также могу привести пример того, как расширение php мешает: Z-Ray Zend Server. Если сбор данных включен, использование памяти будет увеличиваться на каждой итерации, как если бы сборка мусора была отключена.
источник