此前我们了解过 java 和 python 如何管理内存以及运行过程中的垃圾收集。 python 的内存管理与垃圾收集 java 的存活判定与垃圾收集
本篇日志我们来介绍博主另一个比较熟悉的语言 — php 的内存管理方式以及垃圾回收机制。
所有的 php 类型在 php 内部都是用一个 zval 结构存储的,下面是 zval 的存储结构 _zval_struct:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
其中联合体“_zvalue_value”用于表示PHP中所有变量的值,这里之所以使用union,是因为一个zval在一个时刻只能表示一种类型的变量。 可以看到,_zval_struct 中除了 zvalue_value 类型的变量值和 zend_uchar 类型的变量类型数据以外,还存储了 refcount__gc 和 is_ref__gc 两个字段。 1. is_ref — is_ref 是一个 bool 类型的值,他用来标识该变量是否属于引用集合(reference set) 2. refcount — 引用计数器
通过 xdebug_debug_zval 函数可以查看变量的完整信息:
<?php
$a = "new string";
xdebug_debug_zval('a');
?>
打印出了:
a: (refcount=1, is_ref=0)='new string'
与 python 一样,php 也是通过引用计数法来实现内存的回收的,变量中的 refcount 字段就是为了实现这一目的存在的。 当一个对象的引用计数变为 0 时, 它被垃圾回收。 当然,与 python 已经其他使用引用计数法作为垃圾回收机制的语言一样,PHP 的垃圾回收机制也存在循环引用问题。
下面的例子说明了这一情况:
<?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)=… )
在 php 执行过程中,循环引用的变量的引用计数永远不可能减到0,也就永远不会被引用计数规则的垃圾回收器回收,这样就造成了内存泄漏。
PHP5.3.0 实现了回收周期算法,从而处理了因为循环引用造成的内存泄漏问题。 可以通过在配置文件中指定 zend.enable_gc 来修改是否进行该算法的垃圾回收。
这个算法的主要步骤如下: 1. 算法建立了一个根缓冲区,所有的 zval 变量容器都存放在根缓冲区中,如下图紫色部分 2. 当缓冲区满时,垃圾回收器遍历整个根缓冲区,将所有根缓冲区中的变量全部模拟删除 3. 模拟恢复每个已经被模拟删除的变量,原则是只恢复模拟删除后引用计数大于 0 的变量 4. 清除所有在模拟恢复步骤中没有被恢复的变量
由于在对象全部被模拟删除,循环引用的对象的引用计数会相应减少到 0,从而解决了循环引用造成的内存泄漏问题。
http://php.net/manual/zh/features.gc.php。 https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf。