Диагностика утечек памяти. Допустимый размер памяти в # байт.

Я столкнулся с ужасным сообщением об ошибке, возможно, благодаря кропотливой работе, у 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 } с 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 могут помочь мне найти и устранить проблему?

Solutions Collecting From Web of "Диагностика утечек памяти. Допустимый размер памяти в # байт."

У PHP нет сборщика мусора. Он использует подсчет ссылок для управления памятью. Таким образом, наиболее распространенным источником утечек памяти являются циклические ссылки и глобальные переменные. Боюсь, если вы используете фреймворк, у вас будет много кода, чтобы его найти. Самый простой инструмент – выборочно разместить вызовы на memory_get_usage и сузить его до места, где протекает код. Вы также можете использовать xdebug для создания следа кода. Запустите код с трассировкой выполнения и show_mem_delta .

В php существует несколько возможных точек утечки памяти:

  • сам php
  • расширение php
  • Библиотека php, которую вы используете
  • ваш php-код

Трудно найти и исправить первые 3 без глубокой обратной инженерии или знания исходного кода php. Для последнего вы можете использовать бинарный поиск для кода утечки памяти с памятью_get_usage

Я заметил один раз в старом скрипте, что 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.

Недавно я столкнулся с этой проблемой в приложении, под тем, что я собираю, чтобы быть похожими обстоятельствами. Скрипт, который работает в CLI PHP, который перебирает много итераций. Мой скрипт зависит от нескольких базовых библиотек. Я подозреваю, что причиной является определенная библиотека, и я потратил несколько часов напрасно, пытаясь добавить соответствующие методы деструкции в свои классы безрезультатно. Столкнувшись с длительным процессом преобразования в другую библиотеку (которая может оказаться той же проблемой), я придумал грубую работу по этой проблеме в моем случае.

В моей ситуации, в 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 } 

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

Я столкнулся с одной и той же проблемой, и я решил заменить foreach на регулярной основе. Я не уверен в специфике, но кажется, что foreach создает копию объекта (или как-то новую ссылку). Используя регулярный цикл, вы напрямую обращаетесь к элементу.

Вот трюк, который мы использовали для определения того, какие сценарии используют большую часть памяти на нашем сервере.

Сохраните следующий фрагмент в файле, например, /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 прежде чем ваш веб-пользователь сможет записать в файл журнала.

Недавно я заметил, что функции лямбда PHP 5.3 оставляют лишнюю память, используемую при их удалении.

 for ($i = 0; $i < 1000; $i++) { //$log = new Log; $log = function() { return new Log; }; //unset($log); } с for ($i = 0; $i < 1000; $i++) { //$log = new Log; $log = function() { return new Log; }; //unset($log); } 

Я не уверен, почему, но, кажется, он принимает дополнительные 250 байт каждый лямбда даже после удаления функции.

Если то, что вы говорите о том, что PHP выполняет только GC после функции, истинно, вы можете обернуть содержимое цикла внутри функции в качестве обходного пути / эксперимента.

Одна огромная проблема, с которой я столкнулся, – это использовать 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 Mb

И этот скрипт запускался в CLI, но он создавал экземпляр Zend_Application и запускал Bootstrap, поэтому он использовал конфигурацию разработки.

Это действительно помогло запустить скрипт с профилированием xDebug

Я бы посоветовал вам проверить руководство php или добавить gc_enable() для сбора мусора … Это утечка памяти не влияет на то, как работает ваш код.

PS: php имеет сборщик мусора gc_enable() который не принимает аргументов.

Я немного опаздываю на этот разговор, но я расскажу кое-что, относящееся к Zend Framework.

У меня возникла проблема с утечкой памяти после установки php 5.3.8 (с использованием phpfarm) для работы с ZF-приложением, которое было разработано с помощью php 5.2.9. Я обнаружил, что утечка памяти запускалась в файле httpd.conf Apache, в моем определении виртуального хоста, где сказано SetEnv APPLICATION_ENV "development" . После комментирования этой строки утечки памяти прекратились. Я пытаюсь создать встроенный обходной путь в моем PHP-скрипте (главным образом, определяя его вручную в основном файле index.php).

Я не видел, чтобы это упоминалось здесь, но одна вещь, которая может быть полезна, – это использовать xdebug и xdebug_debug_zval ('variableName'), чтобы просмотреть их.

Я также могу привести пример расширения php в пути: Z-Ray Zend Server. Если сбор данных разрешен, использование памяти будет накладываться на каждую итерацию так же, как если бы сбор мусора был отключен.