PHP の循環参照はメモリ リークの一般的な原因です。循環参照は、オブジェクトが直接または間接的に相互に参照するときに発生します。幸いなことに、PHP には循環参照を検出してクリーンアップできるガベージ コレクターが備わっています。ただし、これにより CPU サイクルが消費され、アプリケーションの速度が低下する可能性があります。
ガベージ コレクターは、メモリ内に 10,000 個のループ オブジェクトまたは配列が存在し、そのうちの 1 つがスコープ外になるとトリガーされます。
大量のメモリを使用するオブジェクトの数が少ない場合、ガベージ コレクションはトリガーされません。ガベージ コレクターが収集するはずの孤立したオブジェクトによってメモリが使用されている場合でも、メモリ制限に達する可能性があります。
これが、循環参照を作成する状況を特定し、回避する必要がある理由です。
Web アプリケーションの場合、ガベージ コレクターを無効にし、応答の送信後に PHP がすべてのメモリを解放できるようにするのが理想的です。ただし、これは、デーモンやワーカー プロセスなどの長時間実行されるスクリプトにとっては危険です。時間の経過とともにメモリ リークが蓄積し、ガベージ コレクターへの頻繁な呼び出しによってアプリケーションの速度が低下する可能性があるからです。
この記事では、クロージャとジェネレータが循環参照を保存する方法と、それを防ぐ方法について説明します。
<code class="language-php">class A { public B $b; public function __construct() { $this->b = new B($this); } } class B { public function __construct(public A $a) {} }</code>
この例では、A と B は相互に参照します。 A のインスタンスを作成すると、A を参照する B のインスタンスが作成されます。これにより、循環参照が作成されます。
循環参照を検出するには、gc_collect_cycles()
を使用してガベージ コレクターを手動でトリガーし、gc_status()
を使用して収集された参照の数を読み取ります。
<code class="language-php">// 创建的对象但未分配给变量 new A(); gc_collect_cycles(); print_r(gc_status());</code>
これは出力します:
<code>Array ( ... [collected] => 2 ... )</code>
この例は、ガベージ コレクターが循環参照を持つ 2 つのオブジェクトを検出し、削除したことを示しています。
xdebug_debug_zval()
関数を使用して、オブジェクトへの参照の数を表示することもできます。
循環参照が発生した場合の簡単な解決策は、弱い参照を使用することです。弱参照は、ガベージ コレクターが参照するオブジェクトを収集するのを妨げない参照を保持するオブジェクトです。 PHP では、WeakReference
クラスを使用して弱い参照を作成できます。
これにはコードにいくつかの変更が必要です。クラス B は、A オブジェクトの代わりに WeakReference
オブジェクトを格納するようになりました。 WeakReference
オブジェクトの get()
メソッドを使用して A オブジェクトにアクセスする必要があります。
<code class="language-php">class A { public B $b; public function __construct() { $this->b = new B($this); } } class B { /** @var WeakReference<a> $a */ public WeakReference $a; public function __construct(A $a) { $this->a = WeakReference::create($a); } }</code>
<code class="language-php">// 创建的对象但未分配给变量 new A(); gc_collect_cycles(); print_r(gc_status()); // [collected] => 0</code>
出力では、収集された引用の数が 0 になっていることがわかります。
ヒント 1: 循環参照を防ぐために必要な場合にのみ弱い参照を使用します。
PHP におけるクロージャーの概念は、親スコープ内の変数にアクセスできる関数を作成することです。注意しないと、循環参照が発生する可能性があります。
<code class="language-php">class A { public B $b; public function __construct() { $this->b = new B($this); } } class B { public function __construct(public A $a) {} }</code>
この例では、クロージャ $a->b
は親スコープ内の変数 $a
を参照します。循環参照は、参照が明確であるため、簡単に見つけることができます。
ただし、クロージャの短縮構文を使用すると、より微妙な方法で同じ問題が発生する可能性があります。アロー関数では、変数 $a
はクロージャ内で明示的に参照されませんが、参照によってキャプチャされます。
<code class="language-php">// 创建的对象但未分配给变量 new A(); gc_collect_cycles(); print_r(gc_status());</code>
この例では、収集された参照の数は 2 であり、循環参照を示しています。
クラスメソッド内で作成された非静的クロージャは、$this
がアクセスされない場合でも、オブジェクト インスタンス ($this
) への参照を持ちます。
<code>Array ( ... [collected] => 2 ... )</code>
これは、$this
参照が常にクロージャ内の参照によってキャプチャされるためです。 Reflection::getClosureThis()
を使用してアクセスできます。
<code class="language-php">class A { public B $b; public function __construct() { $this->b = new B($this); } } class B { /** @var WeakReference<a> $a */ public WeakReference $a; public function __construct(A $a) { $this->a = WeakReference::create($a); } }</code>
クロージャがグローバル スコープまたは静的メソッドから作成された場合、$this
参照は null です。
ヒント 2:
$this
が必要ない場合は、常にstatic function () {}
またはstatic fn () =>
を使用してクロージャーを作成します。
この記事を書いた理由について話しましょう。最近、次のようなことを発見しました。 ジェネレーターは、参照が枯渇しない限り参照を保持します。
この例では、クラスはジェネレーターをプロパティに格納しますが、ジェネレーターにはオブジェクト インスタンスへの $this
参照があります。
ジェネレーターはクロージャーのように動作し、オブジェクト インスタンスへの参照を保持します。
<code class="language-php">// 创建的对象但未分配给变量 new A(); gc_collect_cycles(); print_r(gc_status()); // [collected] => 0</code>
クラス インスタンスは、オブジェクト インスタンスへの参照を持つジェネレーターへの参照を持っているため、ガベージ コレクターによって収集されます。
ジェネレーターが使い果たされると、参照が解放され、オブジェクト インスタンスがメモリから削除されます。
<code class="language-php">function createCircularReference() { $a = new stdClass(); $a->b = function () use ($a) { return $a; }; return $a; }</code>
ヒント 3: 反復を通じて常にジェネレーターを使い果たします。
ヒント 4: オブジェクト インスタンスへの参照が保持されないように、静的メソッドまたはクロージャーを使用してジェネレーターを作成します。
循環参照は、PHP におけるメモリ リークの一般的な原因です。ガベージ コレクターが循環参照を検出してクリーンアップできる場合でも、CPU サイクルを消費し、アプリケーションの速度が低下する可能性があります。このような循環参照を作成する状況を検出し、それを防ぐようにコードを調整する必要があります。弱い参照を使用すると参照循環を防ぐことができますが、最初から参照循環を防ぐには、いくつかの簡単なヒントが役に立ちます。
$this
が必要ない場合は、static function () {}
または static fn () =>
を使用してクロージャを作成します。 以上がPHP クロージャとジェネレータは循環参照を保持できますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。