墜落日記 - 2009年7月8日の墜落
今回はメモリリークと戦いまっす!
面白い記事があった。
大型汎用機なんかつまらないぜっ!
最新技術でバリバリ構築だっ!!
とか言っている連中や、最新技術が大好きでそれをビジネスの世界に無秩序に持ち込もうとする現代の風潮に色々と思うところがあった自分だが。
なんか自分の危惧をそのまま代弁してくれたような記事である。
元々はメインフレームで構築されたレガシーシステムを短絡的に罪悪だと定義して、どうオープン系に入れ替えていくかという論調で始めたかったようだが、最終的には常々自分が考えていた「オープン系は安い」の嘘や、オープン系で基幹システムを構築する場合の予想外のリスク、「オープン系レガシーシステム」の存在などが語られている。
アルファギークでも新技術を追っかけるのも良いけど、それだけで企業の基幹業務を遂行するシステムを構築すると思わぬところで足下をすくわれかねない。
メインフレームを駆逐されるべき害悪だと短絡的に考えている技術者には一読をお勧めする。
さて、今回の本題は別にあって、PHP のメモリ管理の話である。
PHP では変数で利用されているメモリは自動的に解放される。
変数は実際には変数の実際の情報を格納しているメモリ領域への参照で、メモリ領域には参照カウンタが存在する。
最初に変数を定義した際にはメモリ領域の参照カウンタが 1 となり、別の変数からメモリ領域が参照されたりすると参照カウンタが +1 される。
メモリ領域を参照している変数がスコープから抜けるなどして解放されると参照カウンタが -1 され、それが 0 になった時点でメモリ領域も解放される。
PHP の場合、ガーベージコレクションは変数の利用停止から遅延して起こるのではなく、即座に起こるようだ。
しかし参照カウンタがうまく 0 にならないケースが存在する。
単純な話だが、循環参照が起こった時だ。
例えば、クラスAのフィールド変数に配列を保持し、その配列にクラスBのインスタンスへの参照を保持するリストクラスを考える。
この時、クラスAはリストを制御する情報を保持する必要があるため、クラスBのインスタンスへの参照を保持する―――これは当然。
しかしクラスBの方から自分自身を保持するリストを識別するためにクラスAのインスタンスへの参照を保持したり何かすると、ここに循環参照が発生する。
そうすると、クラスAのインスタンスとクラスBのインスタンスがお互いの参照カウンタを握りあってガーベージコレクションに引っ掛からなくなる。
これ、自作の PHP 基盤ライブラリであるところの Ag:PURE 2.0 に装備されるデータベース接続の抽象化ライブラリで起こった。
JDBC の様に準備されたステートメントに変数をバインドする仕組みを隠蔽し、ここに前述の例のような構造を用いたのだけど、別にリソースに割り当てたわけでもデータベースドライバ側からの参照が発生したわけでもないのにメモリに残り続ける。
結果、ステートメントを作って変数を割り当てる処理を繰り返すとメモリが一杯になって PHP が Fatal Error を吐きやがる。
HTTP 上で一回のリクエストで発生する処理が少なかったりするとメモリ上限に達する前に終わって、レスポンスの送出と共にメモリが解放されるから問題ない。
しかし CLI を利用してバッチ処理なぞ書いてみると、これがアっと言う間にメモリ上限に辿り着いてしまう。
仕方ないからステートメントを閉じる際にバインドした変数を管理するクラスのインスタンスを保持した変数を片っ端から unset してやるようにした。
ガーベージコレクションが走らないならデストラクタも走らないから、このタイミングしかない。
当たり前の話だが、ガーベージコレクションを前提とした言語はガーベージコレクションの仕様を知らないと思わぬ怪我をすることがある、ということである。
JAVA でガーベージコレクションが度々問題になるように、CLI を実装した PHP も実はガーベージコレクションを考えなければならない局面に入ったわけである。
ま~ PHP でバッチ処理を書く奴も希だろうけど(笑)
コメントは投稿されていません。