メモリリークについて
Webmanは常駐メモリフレームワークであるため、メモリリークの状況に少し注意を払う必要があります。しかし、開発者はあまり心配する必要はありません。メモリリークは非常に極端な条件下で発生し、簡単に回避できます。Webmanの開発は、従来のフレームワーク開発の体験と基本的に同じであり、メモリ管理のために余分な操作を行う必要はありません。
ヒント
Webman に付属の monitor プロセスは、すべてのプロセスのメモリ使用状況を監視します。プロセスの使用メモリがphp.ini
で設定されたmemory_limit
の値に達しそうになると、自動的に対応するプロセスを安全に再起動し、メモリを解放します。この間、業務には影響がありません。
メモリリークの定義
リクエストが増加し続けると、Webmanが占有するメモリも無限に増加します(注意:無限に増加です)、数百MB、さらにはそれ以上に達する場合、これがメモリリークです。もしメモリが増加しても、その後増加しない場合は、メモリリークとは見なされません。
一般的に、プロセスが数十MBのメモリを占有することは正常な状況です。プロセスが非常に大きなリクエストを処理したり、大量の接続を維持したりする場合、単一プロセスのメモリ占有が数百MBに達することもよくあります。この部分のメモリは、PHPが必ずしもすべてをオペレーティングシステムに返却しない場合があります。むしろ、再利用のために残されるので、特定の大きなリクエストを処理した後にメモリ占有が増大し、メモリが解放されない場合があり、これは正常な現象です。(gc_mem_caches()
メソッドを呼び出すと、部分的に未使用のメモリを解放できます。)
メモリリークはどのように発生するのか
メモリリークが発生するためには、以下の2つの条件を満たす必要があります:
- 長寿命の配列が存在する(注意:長寿命の配列であり、通常の配列は問題ありません)
- その長寿命の配列が無限に膨張する(ビジネスが無限にデータを挿入し、データを清掃しない)
条件1と条件2が同時に満たされる場合(注意:同時に満たされる)、メモリリークが発生します。それ以外の場合は、上記の条件を満たさないか、いずれか一方の条件のみを満たす場合、メモリリークとは見なされません。
長寿命の配列
Webmanにおける長寿命の配列は次の通りです:
static
キーワードの配列- シングルトンの配列属性
global
キーワードの配列
注意
Webmanでは長寿命のデータを使用することが許可されていますが、データ内の要素が有限であり、無限に膨張しないことを保証する必要があります。
以下にそれぞれの例を示します。
無限に膨張するstatic配列
class Foo
{
public static $data = [];
public function index(Request $request)
{
self::$data[] = time();
return response('hello');
}
}
static
キーワードで定義された $data
配列は長寿命の配列であり、サンプル内の $data
配列はリクエストに応じて不断に増え続け、メモリリークを引き起こします。
無限に膨張するシングルトン配列属性
class Cache
{
protected static $instance;
public $data = [];
public function instance()
{
if (!self::$instance) {
self::$instance = new self;
}
return self::$instance;
}
public function set($key, $value)
{
$this->data[$key] = $value;
}
}
呼び出しコード
class Foo
{
public function index(Request $request)
{
Cache::instance()->set(time(), time());
return response('hello');
}
}
Cache::instance()
はCacheのシングルトンを返します。これは長寿命のクラスインスタンスであり、$data
属性は static
キーワードを使用していないものの、クラス自体が長寿命であるため、$data
も長寿命の配列になります。異なるkeyのデータを $data
配列に追加し続けることで、プログラムが使用するメモリも増大し、メモリリークを引き起こします。
注意
もしCache::instance()->set(key, value)
で追加されるkeyの数が有限であれば、メモリリークは発生しません。なぜなら、$data
配列は無限に膨張していないからです。
無限に膨張するglobal配列
class Index
{
public function index(Request $request)
{
global $data;
$data[] = time();
return response($foo->sayHello());
}
}
global
キーワードで定義された配列は、関数やクラスメソッドの実行が終了した後も解放されないため、長寿命の配列です。上記のコードは、リクエストが増え続けるにつれてメモリリークを引き起こします。同様に、関数やメソッド内で static
キーワードで定義された配列も長寿命の配列であり、もし配列が無限に膨張すればメモリリークが発生します。例えば:
class Index
{
public function index(Request $request)
{
static $data = [];
$data[] = time();
return response($foo->sayHello());
}
}
推奨
開発者はメモリリークに特に注意を払う必要はありません。なぜなら、発生頻度は非常に低いためです。不幸にも発生した場合、負荷テストを通じてどのコードがリークを生じているか特定し、問題を見つけることができます。たとえ開発者がリークの原因を見つけられなくても、Webman に付属の monitor サービスが、その都度安全にメモリリークが発生したプロセスを再起動し、メモリを解放します。
もしメモリリークを避けたいのであれば、以下の提案に従うことを検討してください。
global
やstatic
キーワードの配列を極力使用しない。使用する場合は、無限に膨張しないことを確認してください。- よく知らないクラスについては、シングルトンを使用せず、
new
キーワードで初期化してください。シングルトンが必要な場合、その配列属性に無限膨張がないか確認してください。