background image

<?php

$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 销毁,这部分内存就泄露了。
这里特别要指出的是,

PHP 是通过符号表(Symbol Table)存储变量符号的,全局有一

个符号表,而每个复杂类型如数组或对象有自己的符号表,因此上面代码中,

a 和 a[0]

是两个符号,但是

a 储存在全局符号表中,而 a[0]储存在数组本身的符号表中,且这里

a 和 a[0]引用同一个 zval(当然符号 a 后来被销毁了)。希望读者朋友注意分清符号

Symbol)的 zval 的关系。

PHP 只用于做动态页面脚本时,这种泄露也许不是很要紧,因为动态页面脚本的生命周

期很短,

PHP 会保证当脚本执行完毕后,释放其所有资源。但是 PHP 发展到目前已经不仅

仅用作动态页面脚本这么简单,如果将

PHP 用在生命周期较长的场景中,例如自动化测试

脚本或

deamon 进程,那么经过多次循环后积累下来的内存泄露可能就会很严重。这并不

是我在耸人听闻,我曾经实习过的一个公司就通过

PHP 写的 deamon 进程来与数据存储

服务器交互。

由于

Reference Counting 的这个缺陷,PHP5.3 改进了垃圾回收算法。

PHP5.3

——

中的垃圾回收算法

Concurrent Cycle Collection in Reference Counted 

Systems