PHP でメモリが足りなくなったら #php

メモリが足りなくて死んじゃう

↓みたいなメッセージが出て死んじゃうことがある。

PHP Fatal error: Out of memory (allocated 39845888) (tried to allocate 512000 bytes)...
Fatal error : Allowed memory size of 2147483648 bytes exhausted (tried to allocate 80 bytes) in ・・・

メモリがどれくらい使われてるのか調べる

memory_get_usage や memory_get_peak_usage を使うと、使用しているメモリを調べられる。

real_usage を引数で指定することにより、確保しているメモリか実際に使っているメモリか判別できる。

echo "memory_get_usage: " . memory_get_usage() / (1024 * 1024) . "MB";
echo "memory_get_peak_usage: " . memory_get_peak_usage() / (1024 * 1024) . "MB";

getrusage というのもあるらしい

メモリ上限の設定を確認する、変更する

php のメモリ上限は設定ファイルで定義されているので、値を変更することで上限を引き上げられる。

-1 にすると上限がなくなる(必要なら使えるだけ使う)。

$ php -i | grep memory_limit
memory_limit => 128M => 128M

php.ini を修正してあげればOK

; Maximum amount of memory a script may consume  
; http://php.net/memory-limit      
; memory_limit = 128M 
memory_limit = 256M

メモリの上限を動的に書き換える

ini_set で memory_limit を変更することによって、プログラムからメモリ上限を引き上げることができる。

// 取得
$memory_limit = ini_get('memory_limit');

// 設定
ini_set('memory_limit', '1G');

GCを強制的に実行する

メモリリークをしているような処理はないのに、まるでメモリリークをしているような現象に遭遇することがある。

そういう場合は gc_collect_cycles で解決するかもしれない。

gc_collect_cycles();

PHPの循環参照の検出はルートが満杯になると動作するが、その件数が1万件に固定されている。

そのため、「件数は少ないがメモリ消費量が大きい」状態になると、GC が動くより先にメモリ上限に達してしまうことがある(と理解している)。

なので、手動で実行すると問題が解消することがあった。

GCの呼び出しを完全に手動制御する gc_enable や gc_disable といった API もあるが、パフォーマンス上の止むにやまない問題がある場合を除き、あえて使うリスクを犯す必要はないと思う( gc_enable でも gc_collect_cycles は使える)。

おまけ: ECSのメモリ制限でOOM

AWS の ECS タスクで PHP プログラムを実行したところ、 ECS 側のメモリ制限に引っかかって OOM になる事象が発生した。

memory_get_peak_usage で取得したメモリ使用量上は問題はなさそうに見えたのだが、プロセスが実際に使用するメモリはまた別のようだ。

なので、 memory_get_peak_usage だけを元にプロセス自体のメモリ制限をカツカツにしないほうがよい。

stackoverflow.com

参考