参考 http://php.net/
php zval数据结构
struct _zval_struct { zvalue_value value; /* value */ zend_uint refcount__gc; /* variable ref count */ zend_uchar type; /* active type */ zend_uchar is_ref__gc; /* if it is a ref variable */ }; typedef struct _zval_struct zval;
1.第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。
2.第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。
第一个例子,使用xdebug查看 zval变量容器的内部数据结构。
<?php
$a = "new string";
?>
<?php
xdebug_debug_zval('a');
?>
a: (refcount=1, is_ref=0)='new string'
第二个例子,
增加一个zval的引用计数,通过赋值观察refcount的变化
<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>
a: (refcount=2, is_ref=0)='new string'
第三个例子,减少引用计数来观察zval数据结构内部的变化
<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
?>
输出
a: (refcount=3, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'
复合数据类型
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=1, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42 )
接下来是一个很经典的说明zval内部指针关系的例子:
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>
输出:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=2, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42, 'life' => (refcount=2, is_ref=0)='life' )
图:
接下来就是一个会在php5.3版本之前导致内存泄漏的一个例子
<?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)=... )
上面的输出结果中的"..."说明发生了递归操作, 显然在这种情况下意味着"..."指向原始数组
图:
尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。
php的同步算法处理引用内存泄漏的方法回头再更(暂时没有深入看)
回收周期
php会把可能根(疑似垃圾的跟)放在根缓存区里面(默认大小为10000),调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制。
但是即使关闭了垃圾回收机制,可能根还是会存进根缓存区里面(相比每次检查回收机制是否打开,还不如存进去),但是当缓存区存满之后,如果还有疑似根就不会存进去了,可能造成内存泄漏。
因此 就在你调用gc_disable()函数释放内存之前,先调用gc_collect_cycles()函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。
垃圾回收会带来时间成本的增加,约百分之10(不到)。但是内存溢出会有明显抑制(特别是在大量多个对象互相指向的时候)
在php5.3以上版本,如果发现一个zval容器中的refcount在减少,并没有减到0,PHP会把该值放到缓冲区,当做有可能是垃圾的可疑对象。
具体参看PHP官网 http://php.net/manual/zh/features.gc.performance-considerations.php