web-dev-qa-db-ja.com

メモリリークの診断-許容メモリサイズ#バイトを使い果たしました

私は恐ろしいエラーメッセージに遭遇しましたが、恐らく苦労して、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トリック、またはブードゥー教のデバッグは、問題を見つけて修正するのに役立ちますか?

93
Mike B

PHPにはガベージコレクタがありません。参照カウントを使用してメモリを管理します。したがって、メモリリークの最も一般的な原因は、循環参照とグローバル変数です。フレームワークを使用する場合、それを見つけるためにトロールする多くのコードがあります、私は恐れています。最も簡単な手段は、memory_get_usageを選択的に呼び出し、コードがリークする場所に絞り込むことです。 xdebug を使用して、コードのトレースを作成することもできます。 実行トレース およびshow_mem_deltaを使用してコードを実行します。

46
troelskn

サーバーで最もメモリを使用しているスクリプトを特定するために使用したトリックを次に示します。

次のスニペットを、/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が必要になる場合があります。

10
Quinn Comendant

PHPでメモリリークが発生する可能性のあるポイントがいくつかあります。

  • pHP自体
  • pHP拡張
  • 使用するphpライブラリ
  • あなたのPHPコード

深いリバースエンジニアリングやPHPソースコードの知識がなければ、最初の3つを見つけて修正するのは非常に困難です。最後の1つは、 memory_get_usage を使用して、メモリリークコードのバイナリ検索を使用できます。

9
kingoleg

古いスクリプトで、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。

7
patcoll

私は同じ問題に遭遇し、私の解決策はforeachを通常のforに置き換えることでした。詳細についてはわかりませんが、foreachがオブジェクトのコピー(または何らかの方法で新しい参照)を作成するようです。通常のforループを使用して、アイテムに直接アクセスします。

6
Gunnar Lium

私は最近、アプリケーションでこの問題に遭遇しましたが、同じような状況になると思います。 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
}

明らかに、このアプローチには制限があり、ウサギの仕事を作成するのは簡単だから、この危険性を認識する必要がありますが、まれなケースでは、より良い修正が見つかるまで困難な場所を乗り越えるのに役立つかもしれません、私の場合のように。

6
Nate Flink

Phpマニュアルを確認するか、gc_enable()関数を追加してガベージを収集することをお勧めします...これは、メモリリークがコードの実行方法に影響しないことです。

PS:phpには、引数を取らないガベージコレクタgc_enable()があります。

4
Kosgei

私は最近、PHP 5.3ラムダ関数が削除されたときに余分なメモリが使用されたままになることに気付きました。

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

理由はわかりませんが、関数が削除された後でもラムダごとに250バイト余分にかかるようです。

3
Xeoncross

明示的に言及されていませんでしたが、 xdebug は素晴らしい仕事プロファイリング時間とメモリ2.6 )。生成した情報を取得して、選択したGUIフロントエンドに渡すことができます。 webgrind (時間のみ)、 kcachegrindqcachegrind または、非常に便利なコールツリーとグラフを生成して、さまざまな問題の原因を見つけることができます。

例(qcachegrindの場合): enter image description here

2
SeanDowney

PHP関数がtrueの後にのみGCを行う場合は、回避策/実験としてループの内容を関数内にラップすることができます。

2

私が抱えていた大きな問題の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プロファイリング でスクリプトを実行するのに本当に役立ちました

2
Andy

私はこの会話に少し遅れていますが、Zend Frameworkに関連する何かを共有します。

Php 5.2.9(phpfarmを使用)をインストールした後、php 5.2.9で開発されたZFアプリで動作するようにメモリリークの問題が発生しました。仮想ホストの定義でSetEnv APPLICATION_ENV "development"と書かれているApacheのhttpd.confファイルでメモリリークがトリガーされていることを発見しました。この行をコメントアウトした後、メモリリークは停止しました。私は私のPHPスクリプトでインラインの回避策を考え出そうとしています(主にメインのindex.phpファイルで手動で定義することにより)。

1
fronzee

ここでは言及していませんでしたが、参考になるのは、xdebugとxdebug_debug_zval( 'variableName')を使用してrefcountを確認することです。

邪魔になるPHP拡張機能の例:Zend ServerのZ-Rayも提供できます。データ収集が有効になっている場合、メモリ使用量は、ガベージコレクションがオフであるかのように各反復で膨らみます。

0
HappyDude