background image

zend_uchar type;

/* active type */

zend_uchar is_ref__gc;

};

其中联合体

_zvalue_value”用于表示 PHP 中所有变量的值,这里之所以使用 union,是

因为一个

zval 在一个时刻只能表示一种类型的变量。可以看到_zvalue_value 中只有 5

个字段,但是

PHP 中算上 NULL 有 8 种数据类型,那么 PHP 内部是如何用 5 个字段表示

8 种类型呢?这算是 PHP 设计比较巧妙的一个地方,它通过复用字段达到了减少字段的目
的。例如,在

PHP 内部布尔型、整型及资源(只要存储资源的标识符即可)都是通过 lval

字段存储的;

dval 用于存储浮点型;str 存储字符串;ht 存储数组(注意 PHP 中的数组其

实是哈希表);而

obj 存储对象类型;如果所有字段全部置为 0 或 NULL 则表示 PHP 中的

NULL,这样就达到了用 5 个字段存储 8 种类型的值。
而当前

zval 中的 value(value 的类型即是_zvalue_value)到底表示那种类型,则由

“_zval_struct”中的 type 确定。_zval_struct 即是 zval 在 C 语言中的具体实现,每个
zval 表示一个变量的内存对象。除了 value 和 type,可以看到_zval_struct 中还有两个
字段

refcount__gc 和 is_ref__gc,从其后缀就可以断定这两个家伙与垃圾回收有关。没

错,

PHP 的垃圾回收全靠这俩字段了。其中 refcount__gc 表示当前有几个变量引用此

zval,而 is_ref__gc 表示当前 zval 是否被按引用引用,这话听起来很拗口,这和 PHP 中
zval

Write-On-Copy”机制有关,由于这个话题不是本文重点,因此这里不再详述,

读者只需记住

refcount__gc 这个字段的作用即可。

PHP5.2

——

中的垃圾回收算法

Reference Counting

PHP5.2 中使用的内存回收算法是大名鼎鼎的 Reference Counting,这个算法中文翻译

叫做 引用计数 ,其思想非常直观和简洁:为每个内存对象分配一个计数器,当一个内存
对象建立时计数器初始化为

1(因此此时总是有一个变量引用此对象),以后每有一个新

变量引用此内存对象,则计数器加

1,而每当减少一个引用此内存对象的变量则计数器减

1,当垃圾回收机制运作的时候,将所有计数器为 0 的内存对象销毁并回收其占用的内存。

PHP 中内存对象就是 zval,而计数器就是 refcount__gc。

例如下面一段

PHP 代码演示了 PHP5.2 计数器的工作原理(计数器值通过 xdebug.org 得

到):

<?php

$val1 = 100; //zval(val1).refcount_gc = 1;
$val2 = $val1; //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是 Write on 
copy,当前 val2 与 val1 共同引用一个 zval)
$val2 = 200; //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处 val2 新建了一

zval)

unset($val1); //zval(val1).refcount_gc = 0($val1 引用的 zval 再也不可用,会被 GC 回收)

?>

Reference Counting 简单直观,实现方便,但却存在一个致命的缺陷,就是容易造成内
存泄露。很多朋友可能已经意识到了,如果存在循环引用,那么

Reference Counting 就

可能导致内存泄露。例如下面的代码: