PHP はマネージ言語です。PHP プログラミングでは、プログラマーはメモリ リソースの割り当てと解放を手動で処理する必要がありません (C を使用して PHP または Zend 拡張機能を作成する場合を除く)。これは、PHP 自体がガベージ コレクション メカニズムを実装していることを意味します。ガベージ コレクション)。公式 PHP Web サイト (php.net) にアクセスすると、PHP5 の 2 つの Current Branch バージョン (PHP5.2 と PHP5.3) が別々に更新されていることがわかります。これは、多くのプロジェクトが依然として PHP 5.2 バージョンを使用しているためです。 、5.3 バージョンは 5.2 と完全な互換性がありません。 PHP5.3 では PHP5.2 をベースに多くの改良が加えられており、その中でもガベージコレクションのアルゴリズムは比較的大きな変更となっています。この記事では、PHP5.2 と PHP5.3 のガベージ コレクション メカニズムについてそれぞれ説明し、この進化と改善が PHP を作成するプログラマに与える影響と注意すべき問題について説明します。
PHP 変数とそれに関連付けられたメモリ オブジェクトの内部表現
結局のところ、ガベージ コレクションは変数とそれに関連付けられたメモリ オブジェクトの操作です。そのため、PHP のガベージ コレクション メカニズムについて説明する前に、変数とそのメモリについて簡単に紹介しましょう。 PHP では オブジェクトの内部表現 (C ソース コードでの表現)。
公式 PHP ドキュメントでは、PHP の変数をスカラー型と複合型の 2 つのカテゴリに分類しています。スカラー型にはブール型、整数型、浮動小数点型、文字列が含まれ、複合型には配列、オブジェクト、リソースが含まれます。NULL もあり、これはどの型にも分割されず、別個のカテゴリになります。
これらの型はすべて、PHP 内で zval と呼ばれる構造体によって統一的に表現されます。PHP ソース コードでは、この構造体の名前は「_zval_struct」です。 zval の具体的な定義は、PHP ソース コードの「Zend/zend.h」ファイルにあります。以下は、関連するコードの抜粋です。
<ol class="dp-c"> <li class="alt"><span><span>typedef union _zvalue_value { </span></span></li> <li> <span> long lval; </span><span class="comment">/* long value */</span><span> </span> </li> <li class="alt"> <span> double dval; </span><span class="comment">/* double value */</span><span> </span> </li> <li><span> struct { </span></li> <li class="alt"><span> char *val; </span></li> <li><span> int len; </span></li> <li class="alt"><span> } str; </span></li> <li> <span> HashTable *ht; </span><span class="comment">/* hash table value */</span><span> </span> </li> <li class="alt"><span> zend_object_value obj; </span></li> <li><span>} zvalue_value; </span></li> <li class="alt"><span> </span></li> <li><span>struct _zval_struct { </span></li> <li class="alt"> <span> </span><span class="comment">/* Variable information */</span><span> </span> </li> <li><span> zvalue_value value; </span></li> <li class="alt"> <span class="comment">/* value */</span><span> </span> </li> <li><span> zend_uint refcount__gc; </span></li> <li class="alt"> <span> zend_uchar type; </span><span class="comment">/* active type */</span><span> </span> </li> <li><span> zend_uchar is_ref__gc; </span></li> <li class="alt"><span>}; </span></li> </ol>
PHP ではすべての変数の値を表すために共用体 "_zvalue_value" が使用されます。ここで共用体が使用される理由は、zval が一度に 1 種類の変数しか表すことができないためです。 _zvalue_value には 5 つのフィールドしかないことがわかりますが、PHP には NULL を含めて 8 つのデータ型が存在します。では、PHP は 5 つのフィールドを使用して内部で 8 つの型を表現しているのでしょうか。これは、フィールドの再利用によって実現されています。畑を減らす目的。たとえば、PHP 内では、ブール型、整数、リソース (リソースの識別子が保存されている場合) は lval フィールドを介して保存されます。str は文字列を保存します。 PHP では配列は実際にはハッシュ テーブルです)、obj にはオブジェクトの種類が格納されます。すべてのフィールドが 0 または NULL に設定されている場合、PHP では NULL を意味するため、8 種類の値を格納するために 5 つのフィールドが使用されます。
現在のzvalの値の型(値の型は_zvalue_value)は、「_zval_struct」の型によって決まります。 _zval_struct は、C 言語での zval の特定の実装です。各 zval は変数のメモリ オブジェクトを表します。 value と type に加えて、_zval_struct には refcount__gc と is_ref__gc という 2 つのフィールドがあることがわかります。それらのサフィックスから、これら 2 つはガベージ コレクションに関連していると結論付けることができます。そうです、PHP のガベージ コレクションはこれら 2 つのフィールドに完全に依存しています。このうち、refcount__gc は、現在この zval を参照している変数がいくつかあることを示し、is_ref__gc は、現在の zval が参照によって参照されているかどうかを示します。これは、PHP の zval の「Write-On-Copy」メカニズムに関連しています。このトピックはこの記事の焦点ではないため、ここでは詳しく説明しません。読者は refcount__gc フィールドの役割だけを覚えておく必要があります。
PHP5.2のガベージコレクションアルゴリズム - 参照カウント
PHP5.2で使用されるメモリリサイクルアルゴリズムは、有名な参照カウントです。このアルゴリズムの中国語訳は「参照カウント」と呼ばれます。その考え方は非常に直感的です。 concise: カウンタは各メモリ オブジェクトに割り当てられます。メモリ オブジェクトが作成されると、カウンタは 1 に初期化されます (つまり、新しい変数がこのメモリ オブジェクトを参照するたびに、カウンタは 1 に初期化されます)。このメモリ オブジェクトを参照する変数が減少するたびに、カウンタは 1 ずつ減分されます。ガベージ コレクション メカニズムが動作すると、カウンタが 0 のすべてのメモリ オブジェクトが破棄され、それらが占有しているメモリがリサイクルされます。 PHP のメモリ オブジェクトは zval、カウンタは refcount__gc です。
たとえば、次の PHP コードは、PHP5.2 カウンターの動作原理を示しています (カウンター値は xdebug を通じて取得されます):
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> </span></li><li class="alt"><span class="vars">$val1</span><span> = 100; </span><span class="comment">//zval(val1).refcount_gc = 1; </span><span> </span></li><li><span class="vars">$val2</span><span> = </span><span class="vars">$val1</span><span>; </span><span class="comment">//zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是Write on copy,当前val2与val1共同引用一个zval) </span><span> </span></li><li class="alt"><span class="vars">$val2</span><span> = 200; </span><span class="comment">//zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处val2新建了一个zval) </span><span> </span></li><li><span>unset(</span><span class="vars">$val1</span><span>); </span><span class="comment">//zval(val1).refcount_gc = 0($val1引用的zval再也不可用,会被GC回收) </span><span> </span></li><li class="alt"><span> </span></li><li><span>?> </span></span></li></ol>
参照カウントはシンプルで直感的で、実装が簡単ですが、致命的な欠陥があります。 、それはメモリリークを引き起こしやすいということです。多くの友人は、循環参照がある場合、参照カウントによってメモリ リークが発生する可能性があることに気づいたかもしれません。たとえば、次のコード:
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span> </span></li><li class="alt"><span class="vars">$a</span><span> = </span><span class="keyword">array</span><span>(); </span></li><li><span class="vars">$a</span><span>[] = & </span><span class="vars">$a</span><span>; </span></li><li class="alt"><span>unset(</span><span class="vars">$a</span><span>); </span></li><li><span> </span></li><li class="alt"><span>?> </span></span></li></ol>
このコードは、まず配列 a を作成し、次に a の最初の要素が参照によって a を指すようにします。このとき、a の zval の refcount は 2 になり、その後、変数 a。この時点では、a が最初に指している zval の refcount は 1 ですが、以下の図に示すように、循環自己参照を形成するため、それを操作することはできません。
。