PHP のメモリ管理メカニズムは非常に詳細であり、この点では Java のガベージ コレクション メカニズムに似ています。 C 言語または C++ の場合、ほとんどの場合、プログラマは適用された領域を解放することしかできません。 PHP では、何千もの接続を処理する必要があるため、これらの接続を長期間維持する必要があることがよくあります。これは、C でプログラムが終了するときに、対応するメモリ ブロックがリサイクルされるという事実とは異なります。
したがって、プログラムを作成するときにメモリのリサイクルに注意を払うようにプログラマに依存するだけでは不十分です。PHP には、メモリ リークが発生しないように、接続に関連する内部メモリ管理メカニズムが必要です。
この記事では、まず PHP のメモリメカニズムを紹介します:
C 言語の空間関数 (malloc()、free()、strdup()、realloc()、calloc() など)は、PHP では異なる形式になります。
適用されたメモリを返す: プログラマーの場合、適用されたメモリをすべて返す必要があります。そうしないとメモリ リークが発生します。常に実行する必要がないプログラムでは、プロセス全体が強制終了されると、少量のメモリ リークが終了します。しかし、Web は Apache のように常に実行されています サーバーでは、小さなメモリ リークが最終的にプログラムのクラッシュの原因となります。
エラー処理の例:
エラーを処理する場合、一般的に使用されるメカニズムは、終了または終了、または重大なエラー E_ERROR が発生すると、longjmp() を使用してこのアドレスにジャンプするというものです。しかし、このアプローチでは、ほとんどの場合、メモリ リークが発生します。すべての無料操作がスキップされるためです。 (この問題は C++ にも存在します。クラスを設計するときは、コンストラクターまたはデストラクターにエラー処理関数や警告関数を決して書かないでください。同じ理由で、オブジェクトはすでに破棄または作成段階にあるため、エラー関数の処理が中断される可能性があります。このプロセスはメモリ リークを引き起こす可能性があります)。 次のコードは、この例を示しています。 この例では、void call_function(const char *fname, int fname_len TSRMLS_DC) { zend_function *fe; char *lcase_fname; /* PHP function names are case-insensitive to simplify locating them in the function tables all function names are implicitly * translated to lowercase */ lcase_fname = estrndup(fname, fname_len);//创造一个函数名的副本 zend_str_tolower(lcase_fname, fname_len);//都转换成小写,这样的寻找的时候很方便,这应该也是php函数表中进行函数标识的方式。 if (zend_hash_find(EG(function_table), lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) {?SUCCESS。这个是要在函数表里面寻找待调用的函数。 zend_execute(fe->op_array TSRMLS_CC); } else { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Call to undefined function: %s()", fname); //等同于Trigger_error() } efree(lcase_fname); }は関数呼び出し時に PHP 関数を提供します。 PHP が関数を呼び出すときは、関数テーブル (function_table) で対応する関数を見つける必要があります。検索する前に、その関数を小文字に変換する必要があります。これにより、検索の効率が向上します。 呼び出される関数が zend_hash_find 関数を通じて見つかった場合は、zend_execute を使用してその関数を呼び出します。 haunted が見つからない場合は、見つからないことを示すエラー メッセージがポップアップ表示されます。しかし、ここで問題が発生します。関数を検索するために、関数名文字列の小文字バージョンが作成されていることに注意してください。この文字列は、zend_hash_find 関数が使用されるまで使用され、一度見つからずにエラーが報告されると、必然的にこの文字列に対応するメモリ空間が見つからなくなり、メモリ リークが発生します。
* { * zval *helloval; * MAKE_STD_ZVAL(helloval); * ZVAL_STRING(helloval, "Hello World", 1); * zend_hash_add(EG(active_symbol_table), "a", sizeof("a"), * &helloval, sizeof(zval*), NULL); * zend_hash_add(EG(active_symbol_table), "b", sizeof("b"), * &helloval, sizeof(zval*), NULL); * }このコードは、最初に zval 変数を宣言し、次に MAKE_STD_ZVAL で初期化し、次に ZVAL_STRING を使用して初期値を付加します。次に、この変数には 2 つの変数名が与えられます。 1つ目はa、2つ目はb、2つ目は間違いなく参考になることは間違いありません。しかし、このコードには問題があるはずです。問題は、zend_hash_add を使用した後に対応する参照カウントを更新しなかったことです。 zend はそのような追加の参照が追加されたことを認識しないため、メモリを解放するときに 2 つの解放が必要になる可能性があります。所以经过修改之后的正确代码如下:
* { * zval *helloval; * MAKE_STD_ZVAL(helloval); * ZVAL_STRING(helloval, "Hello World", 1); * zend_hash_add(EG(active_symbol_table), "a", sizeof("a"), * &helloval, sizeof(zval*), NULL); * <strong>ZVAL_ADDREF(helloval);</strong>//加上这个之后,就不会有重新释放同一块内存空间这样的错误了 * zend_hash_add(EG(active_symbol_table), "b", sizeof("b"), * &helloval, sizeof(zval*), NULL); * }进行了ZVAL_ADDREF之后,下一次unset变量的时候,会先查看ref_count引用计数,如果=1就释放,如果>1就只是-1,并不进行内存释放。
<?php $a = 1; $b = $a; $b += 5; ?>很显然在第二行的时候b声明了一个a的引用,那么在执行完了第三行的代码之后,b增加了,a增不增加呢?很多时候可能并不想增加。所以这个时候当Zend检测到refCount>1之后,就会执行一个变量分离的操作,把原来的一块内存变成两块内存:
zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC) { zval **varval, *varcopy; if (zend_hash_find(EG(active_symbol_table), varname, varname_len + 1, (void**)&varval) == FAILURE) { /*符号表里没找到 */ return NULL; } if ((*varval)->refcount < 2) { /* varname 是唯一的引用,什么也不用做 */ return *varval; } /* 否则的话,不是唯一的引用,给zval*做一个副本 */ MAKE_STD_ZVAL(varcopy); varcopy = *varval; /* Duplicate any allocated structures within the zval* */ zval_copy_ctor(varcopy); //这一块是怎么拷贝的?mark 应该已经跟varval对应的varname连起来了 /* 把varname的版本删掉,这会减少varval的引用次数 */ zend_hash_del(EG(active_symbol_table), varname, varname_len + 1); /* 初始化新创造的值的引用次数,然后附给varname变量 */ varcopy->refcount = 1; varcopy->is_ref = 0; zend_hash_add(EG(active_symbol_table), varname, varname_len + 1, &varcopy, sizeof(zval*), NULL); /* Return the new zval* */ return varcopy; }首先看到了两个判断语句,第一个判断语句先在符号表里面看看有没有找到相应的变量,如果没找到也就没必要分离了。第二个判断语句是看输入的变量的引用次数是不是小于2,如果是的话那就说明输入变量*varval是唯一的,也没必要分离。 否则的话肯定有引用,这个时候就要制作一个副本varcopy。这个副本会承袭varname对应的值,但是不同之处在于帮它重新申请了内存空间,重新初始化了refcount和is_ref参数。 以a、b为例,在$b+=5,执行之后,b作为varname去寻找是否有引用,发现还有一个引用a,这个时候就把b的值拷出来,然后重新申请一片空间,在重新注册为b。这样的话就是两块独立的内存块了。
<?php $a = 1;//执行完这一句之后,a变量的ref_count是1,is_ref是0 $b = &$a;//这一句之后,变量(zval*)的ref_count是2,然后由于显示的&,is_ref为1 $b += 5;// 这个时候在执行这一句的时候就不会有任何的分离 ?>如果你觉得想要a跟着b一起改变,那没有问题,只要显式的用&符号进行引用声明就可以了。这样的话is_ref标志位就会被置1. 这时候也就没必要进行内存块的分离了。所以在上面的代码中要把第二个if语句的判断更改一下:
if ((*varval)->is_ref || (*varval)->refcount < 2) { /* varname is the only actual reference, * or it's a full reference to other variables * either way: no separating to be done */ return *varval; }
<?php $a = 1; $b = $a; $c = &$a; ?>既不是copy on write也不是change on wirte,那没办法了,只好分离一下。这里只好b独立出来了: