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


98

私は恐ろしいエラーメッセージに遭遇しました。恐らく骨の折れる努力で、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
}

この質問の目的のための最悪のスパゲッティコード想像がグローバルスコープのどこかに隠れていると仮定してみましょう$userTask

問題を見つけて修正するのに役立つツール、PHPのトリック、またはブードゥーのデバッグは何ですか?


PS-私は最近、このタイプの問題で問題に遭遇しました。残念ながら、phpには子オブジェクトの破壊の問題があることもわかりました。親オブジェクトの設定を解除すると、その子オブジェクトは解放されません。すべての子オブジェクト__destructなどへの再帰呼び出しを含む変更されたunsetを使用していることを確認する必要があります。詳細はこちら:paul-m-jones.com/archives/262 ::私は次のようなことをしています:function super_unset($ item){if(is_object($ item)&& method_exists($ item、 "__destruct")){$ item-> __ destruct(); } unset($ item); }
Josh

回答:


48

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


3
ただし、注意してください...生成されるトレースファイルは膨大な量になります。Zend Frameworkアプリで初めてxdebugトレースを実行したとき、実行にかなりの時間がかかり、マルチGB(kbまたはMB ... GBではない)サイズのファイルが生成されました。これに注意してください。
rg88 09年

1
ええ、それはかなり重いです.. GBは少し大きいように聞こえます-大きなスクリプトがない限り。たぶん、数行を処理しようとするかもしれません(リークを特定するには十分でしょう)。また、運用サーバーにxdebug拡張機能をインストールしないでください。
troelskn 2009年

31
5.3以降、PHPには実際にガベージコレクタがあります。一方、メモリプロファイリング機能はxdebug :(
wdev

3
+1がリークを発見しました!循環参照を持つクラス!これらの参照がunset()になると、オブジェクトは期待どおりにガベージコレクションされました。ありがとう!:)
rinogo 2013

@rinogoでは、リークをどのようにして見つけましたか?どのようなステップを踏みましたか?
JohnnyQ 16

11

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

次のスニペットをにあるファイルに保存します/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_logWebユーザーがログファイルに書き込む前に、必要になる場合があります。


8

以前のスクリプトで、私のforeachループの後でも、PHPがスコープ内のように「as」変数を維持することに気づきました。例えば、

foreach($users as $user){
  $user->doSomething();
}
var_dump($user); // would output the data from the last $user 

私がそれを見たので、将来のPHPバージョンがこれを修正したかどうかはわかりません。これが事実である場合、行のunset($user)後でdoSomething()それをメモリからクリアすることができます。YMMV。


13
PHPはC / Java / etcのようなループ/条件をスコープしません。ループ/条件付きで宣言されたものはすべて、ループ/条件付きを終了した後も(設計[?]によって)スコープ内にあります。一方、メソッド/関数は、あなたが期待するようにスコープされます-関数の実行が終了すると、すべてが解放されます。
フランクファーマー

これは仕様によるものだと思いました。それの1つの利点は、ループの後で、特定の基準を満たすなど、最後に見つけたアイテムで作業できることです。
ヨアヒム

可能ですがunset()、オブジェクトの場合、変数が指している場所を変更するだけで、メモリから実際に削除されていないことに注意してください。とにかく範囲外になると、PHPはメモリを自動的に解放します。したがって、より良い解決策(この回答に関しては、OPの質問ではありません)は、短い関数を使用して、ループからその変数にハングアップしないようにすることです。長いです。
リッチコート

@patcollこれはメモリリークとは関係ありません。これは単に配列ポインタが変化するだけです。こちらをご覧ください:バージョン3aのprismnet.com/~mcmahon/Notes/arrays_and_pointers.html
Harm Smits

7

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

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

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


91
あなたの答えは、それが得られた可能性がある一般的なものについてです
TravisO

2
PHP 7.2でもコアphpのメモリリークを修正できないのは残念です。実行時間の長いプロセスを実行することはできません。
Aftab Naveed、2018年

6

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

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


5

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

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


3

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

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

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


2
同じことを言うつもりだった。これは5.3.10(#60139)で修正されています
Kristopher Ives

@KristopherIves、更新ありがとうございます!そうです、これはもはや問題ではないので、今は狂ったようにそれらを使用することを恐れるべきではありません。
Xeoncross

2

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


1
@DavidKullmann実は私は私の答えは間違っていると思います。結局のところ、run()呼び出されるも関数であり、その最後にGCが発生します。
Bart van Heukelom、2012

2

私が抱えていた1つの大きな問題は、create_functionを使用することでした。ラムダ関数と同様に、生成された一時的な名前をメモリに残します。

メモリリークのもう1つの原因(Zend Frameworkの場合)はZend_Db_Profilerです。Zend Frameworkでスクリプトを実行する場合は、無効になっていることを確認してください。たとえば、私は私のapplication.iniに次のようなものがありました:

resources.db.profiler.enabled    = true
resources.db.profiler.class      = Zend_Db_Profiler_Firebug

約25.000クエリ+その前の処理の負荷を実行すると、メモリは素晴らしい128Mb(私の最大メモリ制限)になりました。

設定するだけで:

resources.db.profiler.enabled    = false

20 Mb未満に保つのに十分でした

このスクリプトはCLIで実行されていましたが、Zend_Applicationをインスタンス化してブートストラップを実行していたため、「開発」設定を使用していました。

xDebugプロファイリングでスクリプトを実行するのに本当に役立ちました


2

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

例(qcachegrindの場合): ここに画像の説明を入力してください


1

この会話には少し遅れますが、Zend Frameworkに関連することを共有します。

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


1
問題は、彼がCLIで実行していることです。つまり、Apacheはプロセスにまったく関与していません。
Maxime

1
@Maxime良い点、ありがとう。まあ、うまくいけば、問題を解決しようとしているときにこのページが表示されたので、ランダムなGoogle社員が私がここに残したメモから恩恵を受けることを期待しています。
fronzee 2012年

この質問に対する私の答えを確認してください。おそらくそれもあなたのケースでした。
アンディ

アプリケーションは、環境に応じて異なる構成にする必要があります。"development"環境は、通常、他の環境が持っていないかもしれないことをログ記録&プロファイリングの束を持っています。行をコメントにすると、アプリケーションは代わりにデフォルトの環境を使用します。通常は"production"または"prod"です。メモリリークはまだ存在しています。それを含むコードがその環境で呼び出されていないだけです。
Marco Roy

0

ここでは言及されていませんが、参考になるかもしれない1つのことは、xdebugとxdebug_debug_zval( 'variableName')を使用して参照カウントを確認することです。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.