其中灰色部分表示已经不复存在。由于 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
PHP5.3 的垃圾回收算法仍然以引用计数为基础,但是不再是使用简单计数作为回收
准则,而是使用了一种同步回收算法,这个算法由 IBM 的工程师在论文 Concurrent Cycle
Collection in Reference Counted Systems 中提出。
这个算法可谓相当复杂,从论文 29 页的数量我想大家也能看出来,所以我不打算
(也没有能力)完整论述此算法,有兴趣的朋友可以阅读上面的提到的论文(强烈推荐,这
篇论文非常精彩)。
我在这里,只能大体描述一下此算法的基本思想。
首先 PHP
“
”
会分配一个固定大小的 根缓冲区 ,这个缓冲区用于存放固定数量的
zval,这个数量默认是 10,000,如果需要修改则需要修改源代码 Zend/zend_gc.c 中的常量
GC_ROOT_BUFFER_MAX_ENTRIES 然后重新编译。
由上文我们可以知道,一个 zval 如果有引用,要么被全局符号表中的符号引用,要
么被其它表示复杂类型的 zval 中的符号引用。因此在 zval 中存在一些可能根(root)。这里我
们暂且不讨论 PHP 是如何发现这些可能根的,这是个很复杂的问题,总之 PHP 有办法发
现这些可能根 zval 并将它们投入根缓冲区。
当根缓冲区满额时,PHP 就会执行垃圾回收,此回收算法如下: