PHP 類型
PHP的八種數據類型可點擊查看。
源碼表示
struct _zval_struct {
/* Variable information */
zvalue_value value;
/* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
其中zvalue_value為:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
其中refcount__gc表示當前有幾個變量引用此zval,而is_ref__gc表示當前zval是否被按引用引用,這話聽起來很拗口,這和PHP中zval的“Write-On-Copy”機制有關。
PHP5.2中的垃圾回收算法——Reference Counting
PHP5.2中使用的內存回收算法是大名鼎鼎的Reference Counting,這個算法中文翻譯叫做“引用計數”,其思想非常直觀和簡潔:為每個內存對象分配一個計數器,當一個內存對象建立時計數器初始化為1(因此此時總是有一個變量引用此對象),以后每有一個新變量引用此內存對象,則計數器加1,而每當減少一個引用此內存對象的變量則計數器減1,當垃圾回收機制運作的時候,將所有計數器為0的內存對象銷毀并回收其占用的內存。而PHP中內存對象就是zval,而計數器就是refcount__gc。
例如下面一段PHP代碼演示了PHP5.2計數器的工作原理(計數器值通過xdebug得到):
//zval(val1).refcount_gc = 1;
$val1 = 100;
//zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因為是Write on copy,當前val2與val1共同引用一個zval)
$val2 = $val1;
//zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此處val2新建了一個zval)
$val2 = 200;
//zval(val1).refcount_gc = 0($val1引用的zval再也不可用,會被GC回收)
unset($val1);
內存泄漏
$a = array();
$a[] = & $a;
unset($a);
這段代碼首先建立了數組a,然后讓a的第一個元素按引用指向a,這時a的zval的refcount就變為2,然后我們銷毀變量a,此時a最初指向的zval的refcount為1,但是我們再也沒有辦法對其進行操作,因為其形成了一個循環自引用。由于a之前指向的zval的refcount為1(被其HashTable的第一個元素引用),這個zval就不會被GC銷毀,這部分內存就泄露了。
PHP5.3回收算法改進
PHP5.3的垃圾回收算法仍然以引用計數為基礎,但是不再是使用簡單計數作為回收準則,而是使用了一種同步回收算法,這個算法由IBM的工程師在論文Concurrent Cycle Collection in Reference Counted Systems中提出。
這個算法可謂相當復雜,只能大體描述一下此算法的基本思想。
首先PHP會分配一個固定大小的“根緩沖區”,這個緩沖區用于存放固定數量的zval,這個數量默認是10,000,如果需要修改則需要修改源代碼Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新編譯。
由上文我們可以知道,一個zval如果有引用,要么被全局符號表中的符號引用,要么被其它表示復雜類型的zval中的符號引用。因此在zval中存在一些可能根(root)。這里我們暫且不討論PHP是如何發現這些可能根的,這是個很復雜的問題,總之PHP有辦法發現這些可能根zval并將它們投入根緩沖區。
當根緩沖區滿額時,PHP就會執行垃圾回收,此回收算法如下:
1、對每個根緩沖區中的根zval按照深度優先遍歷算法遍歷所有能遍歷到的zval,并將每個zval的refcount減1,同時為了避免對同一zval多次減1(因為可能不同的根能遍歷到同一個zval),每次對某個zval減1后就對其標記為“已減”。
2、再次對每個緩沖區中的根zval深度優先遍歷,如果某個zval的refcount不為0,則對其加1,否則保持其為0。
3、清空根緩沖區中的所有根(注意是把這些zval從緩沖區中清除而不是銷毀它們),然后銷毀所有refcount為0的zval,并收回其內存。
如果不能完全理解也沒有關系,只需記住PHP5.3的垃圾回收算法有以下幾點特性:
1、并不是每次refcount減少時都進入回收周期,只有根緩沖區滿額后在開始垃圾回收。
2、可以解決循環引用問題。
3、可以總將內存泄露保持在一個閾值以下。