スクリプト言語を使用する最大の利点の 1 つは、その自動ガベージ コレクション メカニズム (メモリの解放) を利用できることです。変数を使用した後にメモリを解放するための処理を行う必要はありません。PHP が自動的に実行します。
もちろん、必要に応じて unset() 関数を呼び出してメモリを解放することもできますが、通常はその必要はありません。
ただし、PHP では、unset() を手動で呼び出した場合でも、メモリが自動的に解放されない状況が少なくとも 1 つあります。詳細については、 http://bugs.php.net/bug.php?id=33595を参照してください。
問題の症状: 2 つのオブジェクト間に「親オブジェクトと子オブジェクト」などの相互参照関係がある場合、親オブジェクトで unset() を呼び出しても、子オブジェクトで親オブジェクトを参照しているメモリは解放されません (親オブジェクトが子オブジェクトであっても)オブジェクトはガベージ コレクションされており、これも機能しません)。
少し混乱していますか?次のコードを見てみましょう:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}}class Bar {function __construct($foo = null){$this-> foo = $foo;}}while (true) {$foo = new Foo();unset($foo);echonumber_format(memory_get_usage()) "n";}?>このコードを実行すると、メモリ使用量が表示されます。使い果たされるまでどんどん高くなっていく。
...33,551,61633,551,97633,552,33633,552,696PHP 致命的エラー: memleak.php の 17 行目で、33554432 バイトの許容メモリ サイズが使い果たされました (16 バイトを割り当てようとしました) ほとんどの PHP プログラマにとって、これはこの状況です問題ありません。
ただし、実行時間の長いコードで相互参照するオブジェクトを多数使用する場合、特にオブジェクトが比較的大きい場合は、メモリがすぐに使い果たされてしまいます。
Userland ソリューションはやや退屈で洗練されていませんが、前述の bugs.php.net リンクが解決策を提供します。
このソリューションでは、オブジェクトを解放する前にデストラクター メソッドを使用して、この目標を達成します。 Destructor メソッドはすべての内部親オブジェクト参照をクリアできます。これは、そうでなければオーバーフローするメモリのこの部分を解放できることを意味します。
「修正後」のコードは次のとおりです:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}function __destruct(){unset($this->bar);}}class Bar {function __construct($foo = null){$this->foo = $foo;}}while (true) {$foo = new Foo();$foo->__destruct();unset($foo);echo number_format(memory_get_usage()) . "n";}?>新しい Foo::__destruct() メソッドと、オブジェクトを解放する前の $foo->__destruct() の呼び出しに注意してください。このコードはメモリ使用量の増加の問題を解決し、コードが正常に動作できるようになりました。
PHPカーネルソリューション?
なぜメモリオーバーフローが起こるのでしょうか?私は PHP カーネルの研究には詳しくありませんが、この問題は参照カウントに関連していると確信しています。
$bar で参照される $foo の参照カウントはデクリメントされません。これは、親オブジェクト $foo が解放されるためです。この時点で、PHP は $foo オブジェクトがまだ必要であると判断するため、メモリのこの部分は解放されません。とか。
ここで私の無知が明らかになりますが、一般的な考え方は、参照カウントはデクリメントされないため、一部のメモリは決して解放されないということです。
前述の bugs.php.net リンクで、ガベージ コレクション プロセスを変更するとパフォーマンスが大幅に犠牲になることがわかりましたが、参照カウントについてはあまり詳しくないので、これが真実だと思いました。
ガベージ コレクション プロセスを変更する代わりに、unset() を使用して内部オブジェクトを解放してはどうでしょうか? (またはオブジェクトを解放するときに __destruct() を呼び出しますか?)
おそらく、PHP カーネル開発者は、このガベージ コレクション メカニズムをここまたは他の場所で変更できるでしょう。
更新: Martin Fjordvald がコメントで、David Wang がガベージ コレクション用に作成したパッチについて言及しました (実際には、「一枚の布全体」のように見えますが、非常に巨大です。詳細については、このメールの最後にある CVS エクスポート情報を参照してください)。が存在し (電子メール)、PHP カーネル開発コミュニティのメンバーから注目を集めています。問題は、このパッチを PHP5.3 に入れるべきかどうかですが、あまりサポートされていません。良い妥協案は、unset() 関数内のオブジェクトの __destruct() メソッドを呼び出すことだと思います。