Диагностика утечек памяти. Допустимый размер памяти в # байт.
Я столкнулся с ужасным сообщением об ошибке, возможно, благодаря кропотливому усилию, у PHP закончилась нехватка памяти:
Разрешенный размер памяти #### байт исчерпан (попытался выделить #### bytes) в файле .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 могут помочь мне найти и устранить проблему?
Ответы
Ответ 1
В PHP нет сборщика мусора. Он использует подсчет ссылок для управления памятью. Таким образом, наиболее распространенным источником утечек памяти являются циклические ссылки и глобальные переменные. Боюсь, если вы используете фреймворк, у вас будет много кода, чтобы его найти. Самый простой инструмент - выборочно разместить вызовы на memory_get_usage
и сузить его до места утечки кода. Вы также можете использовать xdebug для создания следа кода. Запустите код с трассировки выполнения и show_mem_delta
.
Ответ 2
В php существует несколько возможных точек утечки памяти:
- php
- расширение php
- Библиотека php, которую вы используете
- ваш php-код
Трудно найти и исправить первые 3 без глубокой обратной инженерии или знания исходного кода php. Для последнего вы можете использовать двоичный поиск для кода утечки памяти с помощью memory_get_usage
Ответ 3
Я заметил один раз в старом script, что PHP будет поддерживать переменную "as" как в области видимости даже после моего цикла foreach. Например,
foreach($users as $user){
$user->doSomething();
}
var_dump($user); // would output the data from the last $user
Я не уверен, что будущие версии PHP исправлены или нет, так как я видел это. Если это так, вы можете unset($user)
после строки doSomething()
удалить его из памяти. YMMV.
Ответ 4
Недавно я столкнулся с этой проблемой в приложении, под тем, что я собираю, чтобы быть похожими обстоятельствами. A script, который работает в PHP cli, который перебирает много итераций. Мой script зависит от нескольких базовых библиотек. Я подозреваю, что причиной является определенная библиотека, и я потратил несколько часов напрасно, пытаясь добавить к ним подходящие методы деструкции. Столкнувшись с длинным процессом преобразования в другую библиотеку (которая может оказаться в одинаковых проблемах), я придумал грубую работу для этой проблемы в моем случае.
В моей ситуации, в linux cli, я перебирал кучу пользовательских записей и для каждого из них создавал новый экземпляр нескольких классов, которые я создал. Я решил попробовать создать новые экземпляры классов с помощью метода PHP exec, чтобы этот процесс выполнялся в "новом потоке". Вот действительно базовый пример того, что я имею в виду:
foreach ($ids as $id) {
$lines=array();
exec("php ./path/to/my/classes.php $id", $lines);
foreach ($lines as $line) { echo $line."\n"; } //display some output
}
Очевидно, что этот подход имеет ограничения, и нужно знать об опасности этого, так как было бы легко создать работу на кролике, однако в некоторых редких случаях это могло бы помочь преодолеть трудное место, пока не будет лучше исправлено можно найти, как и в моем случае.
Ответ 5
Я столкнулся с одной и той же проблемой, и моим решением было заменить foreach обычным для. Я не уверен в специфике, но кажется, что foreach создает копию объекта (или как-то новую ссылку). Используя регулярный цикл, вы напрямую обращаетесь к элементу.
Ответ 6
Вот трюк, который мы использовали для определения того, какие сценарии используют большую часть памяти на нашем сервере.
Сохраните следующий фрагмент файла в файле, например, /usr/local/lib/php/strangecode_log_memory_usage.inc.php
:
<?php
function strangecode_log_memory_usage()
{
$site = '' == getenv('SERVER_NAME') ? getenv('SCRIPT_FILENAME') : getenv('SERVER_NAME');
$url = $_SERVER['PHP_SELF'];
$current = memory_get_usage();
$peak = memory_get_peak_usage();
error_log("$site current: $current peak: $peak $url\n", 3, '/var/log/httpd/php_memory_log');
}
register_shutdown_function('strangecode_log_memory_usage');
Используйте его, добавив следующее в httpd.conf:
php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php
Затем проанализируйте файл журнала в /var/log/httpd/php_memory_log
Возможно, вам понадобится touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
, прежде чем ваш веб-пользователь сможет записать в файл журнала.
Ответ 7
Недавно я заметил, что функции лямбда PHP 5.3 оставляют лишнюю память, используемую при их удалении.
for ($i = 0; $i < 1000; $i++)
{
//$log = new Log;
$log = function() { return new Log; };
//unset($log);
}
Я не уверен, почему, но, кажется, он принимает дополнительные 250 байтов каждый лямбда даже после удаления функции.
Ответ 8
Если то, что вы говорите о том, что PHP только выполняет GC после функции, является true, вы можете обернуть содержимое цикла внутри функции в качестве обходного пути/эксперимента.
Ответ 9
Я бы посоветовал вам проверить руководство по php или добавить функцию gc_enable()
для сбора мусора... Это утечка памяти не влияет на то, как работает ваш код.
PS: php имеет сборщик мусора gc_enable()
, который не принимает аргументов.
Ответ 10
Одна огромная проблема, с которой я столкнулся, заключалась в использовании create_function. Как и в лямбда-функциях, он оставляет генерируемое временное имя в памяти.
Другой причиной утечки памяти (в случае Zend Framework) является Zend_Db_Profiler.
Убедитесь, что это отключено, если вы запускаете скрипты под Zend Framework.
Например, я использовал в своем приложении application.ini следующее:
resources.db.profiler.enabled = true
resources.db.profiler.class = Zend_Db_Profiler_Firebug
Запустив примерно 25 000 запросов + загрузок до этого, доведя память до хорошего 128 Мб (максимальный предел максимальной памяти).
Просто установив:
resources.db.profiler.enabled = false
этого было достаточно, чтобы сохранить его ниже 20 МБ
И этот script был запущен в CLI, но он создавал экземпляр Zend_Application и запускал Bootstrap, поэтому он использовал конфигурацию разработки.
Это действительно помогло запустить script с профилем xDebug
Ответ 11
Я немного опаздываю на этот разговор, но я расскажу кое-что, относящееся к Zend Framework.
У меня возникла проблема с утечкой памяти после установки php 5.3.8 (с использованием phpfarm) для работы с ZF-приложением, которое было разработано с помощью php 5.2.9. Я обнаружил, что утечка памяти запускалась в файле Apache httpd.conf в определении моего виртуального хоста, где говорится SetEnv APPLICATION_ENV "development"
. После комментирования этой строки утечки памяти прекратились. Я пытаюсь создать встроенный обходной путь в моем PHP скрипт (главным образом, определяя его вручную в основном файле index.php).
Ответ 12
Я не видел, чтобы это упоминалось здесь, но одна вещь, которая может быть полезна, - это использовать xdebug и xdebug_debug_zval ('variableName'), чтобы просмотреть их.
Я также могу привести пример расширения php: Zend Server Z-Ray. Если сбор данных разрешен, использование памяти будет забрасываться на каждой итерации так же, как если бы сбор мусора был отключен.