私は恐ろしいエラーメッセージに遭遇しましたが、恐らく苦労して、PHPはメモリ不足です:
123行目のfile.phpで####バイトのメモリサイズを使い果たしました(####バイトを割り当てようとしました)
自分が何をしているかを知っていて、制限を増やしたい場合は、 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トリック、またはブードゥー教のデバッグは、問題を見つけて修正するのに役立ちますか?
サーバーで最もメモリを使用しているスクリプトを特定するために使用したトリックを次に示します。
次のスニペットを、/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
のログファイルを分析します
Webユーザーがログファイルに書き込む前に、touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
が必要になる場合があります。
PHPでメモリリークが発生する可能性のあるポイントがいくつかあります。
深いリバースエンジニアリングやPHPソースコードの知識がなければ、最初の3つを見つけて修正するのは非常に困難です。最後の1つは、 memory_get_usage を使用して、メモリリークコードのバイナリ検索を使用できます。
古いスクリプトで、PHPがforeachループの後でもスコープ内の "as"変数を保持することに気付きました。たとえば、
_foreach($users as $user){
$user->doSomething();
}
var_dump($user); // would output the data from the last $user
_
将来PHPバージョンがこれを修正したかどうかは私が見たのでわかりません。この場合、unset($user)
はdoSomething()
メモリから消去する行。YMMV。
私は同じ問題に遭遇し、私の解決策はforeachを通常のforに置き換えることでした。詳細についてはわかりませんが、foreachがオブジェクトのコピー(または何らかの方法で新しい参照)を作成するようです。通常のforループを使用して、アイテムに直接アクセスします。
私は最近、アプリケーションでこの問題に遭遇しましたが、同じような状況になると思います。 PHPのCLIで実行されるスクリプトで、多くの反復処理をループします。私のスクリプトは、いくつかの基礎となるライブラリに依存しています。特定のライブラリが原因であると思うので、適切な破壊メソッドをクラスに追加しようとして数時間無駄に費やしました。別のライブラリへの長い変換プロセスに直面したため(同じ問題が発生する可能性があります)、私はこの場合の問題に対して大まかな回避策を思い付きました。
私の状況では、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
}
明らかに、このアプローチには制限があり、ウサギの仕事を作成するのは簡単だから、この危険性を認識する必要がありますが、まれなケースでは、より良い修正が見つかるまで困難な場所を乗り越えるのに役立つかもしれません、私の場合のように。
Phpマニュアルを確認するか、gc_enable()
関数を追加してガベージを収集することをお勧めします...これは、メモリリークがコードの実行方法に影響しないことです。
PS:phpには、引数を取らないガベージコレクタgc_enable()
があります。
私は最近、PHP 5.3ラムダ関数が削除されたときに余分なメモリが使用されたままになることに気付きました。
for ($i = 0; $i < 1000; $i++)
{
//$log = new Log;
$log = function() { return new Log; };
//unset($log);
}
理由はわかりませんが、関数が削除された後でもラムダごとに250バイト余分にかかるようです。
明示的に言及されていませんでしたが、 xdebug は素晴らしい仕事プロファイリング時間とメモリ( 2.6 )。生成した情報を取得して、選択したGUIフロントエンドに渡すことができます。 webgrind (時間のみ)、 kcachegrind 、 qcachegrind または、非常に便利なコールツリーとグラフを生成して、さまざまな問題の原因を見つけることができます。
PHP関数がtrueの後にのみGCを行う場合は、回避策/実験としてループの内容を関数内にラップすることができます。
私が抱えていた大きな問題の1つは、 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のクエリとその前の処理の負荷を実行すると、メモリがNice 128Mb(最大メモリ制限)になりました。
設定するだけで:
resources.db.profiler.enabled = false
20 Mb未満に抑えるには十分でした
また、このスクリプトはCLIで実行されていましたが、Zend_Applicationをインスタンス化し、Bootstrapを実行していたため、「開発」設定を使用しました。
xDebugプロファイリング でスクリプトを実行するのに本当に役立ちました
私はこの会話に少し遅れていますが、Zend Frameworkに関連する何かを共有します。
Php 5.2.9(phpfarmを使用)をインストールした後、php 5.2.9で開発されたZFアプリで動作するようにメモリリークの問題が発生しました。仮想ホストの定義でSetEnv APPLICATION_ENV "development"
と書かれているApacheのhttpd.confファイルでメモリリークがトリガーされていることを発見しました。この行をコメントアウトした後、メモリリークは停止しました。私は私のPHPスクリプトでインラインの回避策を考え出そうとしています(主にメインのindex.phpファイルで手動で定義することにより)。
ここでは言及していませんでしたが、参考になるのは、xdebugとxdebug_debug_zval( 'variableName')を使用してrefcountを確認することです。
邪魔になるPHP拡張機能の例:Zend ServerのZ-Rayも提供できます。データ収集が有効になっている場合、メモリ使用量は、ガベージコレクションがオフであるかのように各反復で膨らみます。