伝統的に、PHP で以前使われていたような参照カウント法記憶機構では、 循環参照メモリ・リークに対処できません。 しかしながら、5.3.0 現在、PHP ではその問題に焦点を当てた » Concurrent Cycle Collection in Reference Counted Systems レポートに由来する同期アルゴリズムを実装しています。
アルゴリズムの動作方法の詳しい説明についてはこのセクションで扱う範囲を超えるので、 ここではその基本を説明します。 まず第一に、いくつかの基本原則を確立しなければなりません。 refcount が増やされたら、それはまだ使用中で、従ってゴミではありません。 refcount が減少して、ゼロに達したら、zvalは解放可能です。 refcount 引数がゼロ以外の値に減少する場合、これは、ガベージサイクルを作成できる だけであることを意味します。 第2に、ガベージサイクルで、それらの refcount を減少させられるかどうかチェック することによって、どの部分がゴミか発見できます。 可能です、そして、zval のうちいずれがゼロの refcountを持つか調べます。
refcount の減少が起こりうるたびにガベージ・サイクルのチェックを呼び出さなくても良いように、 代わりに、可能性があるルート (zvals) 全てをアルゴリズムはルート・バッファにたくわえます。 (それらは「紫」とマークされます) それは、可能性のあるガベージのルートそれぞれがバッファ内で一度だけで終わることも確認します。 ルート・バッファが満杯の場合だけ、内部のそれぞれの zval 全てに対して収集機構が動き出します。 上図のステップ A をご覧ください。
ステップ B では、見つけた各 zval の refcount を一つ減じるために、 可能性があるルート全てに対してアルゴリズムが深さ優先探索を実行します。 それは、同一の zval に対して refcount を2回減じていないことを確保します。 (それらを「灰色」とマークすることにより) 工程 C では、それぞれのルート・ノードからアルゴリズムが再び深さ優先探索を実行します。 それは各 zval の refcount を再度チェックするためです。 refcount がゼロと分かった場合、zval は「白」(図では青色です)とマークされます。 もしそれが正の数なら、それは、指し示すところからの深さ優先探索により refcount の減少を一つ戻します。 そしてそれらは再び「黒」とマークされます。 最後の工程 (D) では、アルゴリズムはルート・バッファを走査してそこから zval ルートを除去します。 そしてその一方で zval が前の工程で「白」とマークされていたかどうかをチェックします。 「白」としてマークされた zval が全て解放されます。
アルゴリズムが動作する方法の基礎を理解したので、 これと PHP を統合する方法を振り返りましょう。 デフォルトで、PHP のガベージコレクタは有効です。 しかしながら、これを変更できる php.ini の設定があります。 それが zend.enable_gc です。
ガベージコレクタがオンの場合、 ルート・バッファが満杯になるといつでも、先に述べたように循環検出法が実行されます。 ルート・バッファでは、可能性があるルートのサイズが一万件に固定されています。 (PHP のソースコードの Zend/zend_gc.c で GC_ROOT_BUFFER_MAX_ENTRIES 定数を変更して、PHP を再コンパイルすると変更できます) ガベージコレクタをオフにすると、循環検出法は実行されなくなります。 しかしながら、可能性があるルートはルート・バッファに常に記録されます。 ガベージコレクション機構がこの構成設定値で起動したかどうかは問題ではありません。
ガベージコレクション機構がオフの時に、可能性があるルートでルート・バッファが満杯になると、 可能性があるルートは単にそれ以上記録されません。 記録されないそれらの可能性があるルートは、アルゴリズムによって決して分析されません。 もしそれらが循環参照サイクルの要素ならば、 それらは決してクリーンアップされないで、メモリリークを生み出します。
たとえ機構が無効だとしても、可能性があるルートが記録される理由は、 可能性があるルートが見つかるたびに機構がオンかどうかチェックしなければいけないよりも、 可能性があるルートを記録するほうが速いからです。 しかしながら、ガベージコレクションと分析機構そのものにかなりの時間がかかることがあります。
zend.enable_gc 構成設定値を変える他に、 gc_enable() や gc_disable() をそれぞれ呼ぶことでも、 ガベージコレクション機構をオン/オフできます。 それらの機能を呼ぶことと、構成設定値で機構をオン/オフすることには同じ影響があります。 たとえ可能性があるルートのバッファがまだ満杯でなくても、 循環の収集を強制することもできます。 このために、gc_collect_cycles() 関数を使用できます。 この関数は、アルゴリズムによって収集された循環の数を返します。
機構をオン/オフしたり、循環の収集を開始する機能の後ろ盾になる根拠は、 アプリケーションの一部分は非常に時間に敏感な可能性があることです。 それらの場合、ガベージコレクション機構が有効にならないほうが良いかもしれません。 もちろん、アプリケーションのある種の部分では、ガベージコレクションをオフにすることにより、 メモリリークを引き起こす危険があります。 なぜなら可能性があるルートの一部は有限のルート・バッファに収まらないかもしれないからです。 従って、ルート・バッファにすでに記録された、可能性があるルートを通じて失われる可能性のあるメモリを解放するために、 gc_disable() を呼ぶ直前に gc_collect_cycles() を呼ぶことは多分賢いでしょう。 そうすると、循環収集機構がオフの間、 可能性があるルートを保管するためのより多くの空間のための空のバッファが残ります。