似乎没有办法检查特定类变量的引用计数,但是您可以使用 xdebug_debug_zval('this'); 查看当前类实例中所有变量的引用计数。
PHP变量存储在一个名为“zval”的容器中。除了变量的类型和值外,zval容器还包含两条附加信息。第一条称为“is_ref”,是一个布尔值,指示变量是否属于“引用集”的一部分。通过此位,PHP引擎能够区分普通变量和引用。由于PHP允许用户层面的引用(例如,由&运算符创建),因此zval容器还具有内部引用计数机制来优化内存使用。第二条附加信息称为“refcount”,包含指向此zval容器的变量名(也称为符号)的数量。所有符号都存储在符号表中,每个作用域都有一个符号表。主脚本(即通过浏览器请求的脚本)有一个作用域,每个函数或方法也有一个作用域。
当使用常量值创建新变量时,会创建一个zval容器,例如:
示例 #1 创建一个新的zval容器
<?php
$a = "new string";
?>
在这种情况下,新的符号名a
在当前作用域中创建,并创建一个新的变量容器,其类型为string,值为new string
。“is_ref”位默认设置为false
,因为没有创建用户层面的引用。“refcount”设置为1
,因为只有一个符号使用此变量容器。请注意,“refcount”为1
的引用(即“is_ref”为true
)被视为非引用(即“is_ref”为false
)。如果您安装了» Xdebug,则可以通过调用xdebug_debug_zval()来显示此信息。
示例 #2 显示zval信息
<?php
$a = "new string";
xdebug_debug_zval('a');
?>
上面的例子将输出
a: (refcount=1, is_ref=0)='new string'
将此变量赋值给另一个变量名将增加refcount。
示例 #3 增加zval的refcount
<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>
上面的例子将输出
a: (refcount=2, is_ref=0)='new string'
这里的refcount是2
,因为同一个变量容器与a和b都链接。PHP足够聪明,在不需要时不会复制实际的变量容器。当“refcount”达到零时,变量容器将被销毁。“refcount”会在任何与变量容器链接的符号离开作用域(例如,当函数结束时)或符号被取消赋值(例如,通过调用unset())时减少1。以下示例演示了这一点
示例 #4 减少zval的refcount
<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
$b = 42;
xdebug_debug_zval( 'a' );
unset( $c );
xdebug_debug_zval( 'a' );
?>
上面的例子将输出
a: (refcount=3, is_ref=0)='new string' a: (refcount=2, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'
如果现在调用unset($a);
,变量容器(包括类型和值)将从内存中删除。
对于array和object等复合类型,情况会稍微复杂一些。与标量值相反,array和object将其属性存储在它们自己的符号表中。这意味着下面的例子创建了三个zval容器
示例 #5 创建一个array zval
<?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容器是:a、meaning和number。增加和减少“refcounts”的规则类似。下面,我们在数组中添加另一个元素,并将它的值设置为已经存在的元素的内容
示例 #6 将已存在的元素添加到数组
<?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' )
或者图形化表示为
从上面的Xdebug输出中,我们可以看到旧的和新的数组元素现在都指向一个“refcount”为2
的zval容器。尽管Xdebug的输出显示了两个值为'life'
的zval容器,但它们是同一个。 xdebug_debug_zval()函数没有显示这一点,但是您可以通过显示内存指针来看到它。
从数组中删除元素就像从作用域中删除符号一样。这样做会减少数组元素指向的容器的“refcount”。同样,当“refcount”达到零时,变量容器将从内存中删除。同样,一个例子来演示这一点
示例 #7 从数组中删除元素
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset( $a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>
上面的例子将输出类似于
a: (refcount=1, is_ref=0)=array ( 'life' => (refcount=1, is_ref=0)='life' )
现在,如果我们将数组本身作为数组的元素添加,情况就会变得有趣,我们将在下一个例子中这样做,我们还会加入一个引用运算符,否则PHP会创建一个副本
示例 #8 将数组作为其自身的元素添加
<?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)=... )
或者图形化表示为
您可以看到数组变量(a)以及第二个元素(1)现在都指向一个“refcount”为2
的变量容器。“...”在上图的显示中表示存在递归,当然,在这种情况下,“...”是指向原始数组。
和之前一样,取消设置变量会移除符号,并且它指向的变量容器的引用计数会减少一。因此,如果我们在运行上述代码后取消设置变量$a,则$a和元素“1”指向的变量容器的引用计数将从“2”减少到“1”。这可以表示为
示例 #9 取消设置 $a
(refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
或者图形化表示为
虽然在任何作用域中都没有符号指向此结构,但它无法被清理,因为数组元素“1”仍然指向同一个数组。由于没有外部符号指向它,用户无法清理此结构;因此会发生内存泄漏。幸运的是,PHP会在请求结束时清理此数据结构,但在那之前,它会占用宝贵的内存空间。如果您正在实现解析算法或其他一些子元素指向“父”元素的情况,这种情况经常发生。当然,对象也可能发生同样的情况,而且实际上更可能发生,因为对象总是隐式地以引用方式使用。
如果这种情况只发生一两次,可能不是问题,但如果发生数千次甚至数百万次这样的内存损失,显然就会成为问题。这在长时间运行的脚本中尤其成问题,例如守护进程(请求基本上永不结束)或大量的单元测试中。后者在运行 eZ Components 库的模板组件的单元测试时造成了问题。在某些情况下,它需要超过 2 GB 的内存,而测试服务器并没有那么多内存。
“示例 #8 将数组本身作为其自身的元素”的结果在 PHP7 中会有所不同
a: (refcount=2, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
代替
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
PHP 7 中的内部值表示
https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
PHP 7.3.12 (cli) 的输出
a: (interned, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
我的 PHP 版本:HP 7.1.25 (cli) (built: Dec 7 2018 08:20:45) ( NTS )
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
输出
a: (refcount=2, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
如果 $a 是字符串值,“refcount”默认为 2。
我的 PHP 版本是 PHP 7.1.6 (cli),当我运行时
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
它显示
a: (refcount=0, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1