PHP

php 垃圾回收机制

深入理解php 垃圾回收机制

Posted by Angkee on July 3, 2018

每个 php 变量存在一个叫”zval”的变量容器中

struct _zval_struct {
    /* Variable information */
    zvalue_value value;        /* value */
    zend_uint refcount__gc;
    zend_uchar type;           /* active type */
    zend_uchar is_ref__gc;
};

一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。

第一个是”is_ref”,是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。

第二个额外字节是”refcount”,用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。

引用计数存在的问题,在 php5.3 之前的版本,循环引用会造成 refcount 无法归零,变量无法被销毁,从而造成内存泄露。

<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );

----
输出
a: (refcount=2, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=2, is_ref=1)=...
)

---

unset($a);

xdebug_debug_zval( 'a' );

----
输出:

(refcount=1, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=1, is_ref=1)=...
)
?>

5.3.0 PHP 引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems)中的同步算法,来处理这个内存泄漏问题。

image

当打开垃圾回收机制时,根缓冲区(root buffer)会存放循环引用的变量,在图A用紫色来标记,称为疑似垃圾,在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。

在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减”1”,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰。

在步骤 C 中,模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉。

算法中都是模拟删除、模拟恢复、真的删除,都使用简单的遍历即可(最典型的深搜遍历)。复杂度为执行模拟操作的节点数正相关,不只是紫色的那些疑似垃圾变量。