PHP变量和内存管理
zval
PHP是弱类型的语言,声明变量不需要指定类型,PHP是怎么做到的呢?
首先,所有变量都是用一个叫做zval的变量容器来保存的,结构如下:
typedef struct _zval_struct {
zvalue_value value; # 变量具体的值
zend_uint refcount; # 引用计数
zend_uchar type; # 变量类型
zend_uchar is_ref; # 是否引用
} zval;
然后value是一个联合体:
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
当我们给变量赋值的时候,PHP自动识别变量类型并保存到type中,然后下一次通过type来决定获取变量的方式。
但是这个zval结构并没有保存变量名称,PHP是怎么找到对应的变量的呢?
当我们创建变量的时候,php会把变量内容保存到zval,然后把变量名称和指向zval的指针保存到一个叫做符合表的东西里面,因此,PHP就可以通过变量名找到对应的zval来获取到变量值
那么is_ref和refcount又是干嘛的?考虑下面的代码:
$a = 'hello world';
$b = $a;
1.首先申请一块内存把变量内容保存到一个zval结构体中,然后把变量名和变量内容的映射关系保存到符合表中
2.复制变量a给变量b,这时候如果php再申请一块内存保存同样的内容到一个新的zval中,有点浪费了,所以就有了引用
那么使用引用以后,第2步是怎么处理的呢?
只需要把b保存到符号表中并且指向和a指向的同一个zval就行了,这样a和b都指向了同一个zval,然后这个zval的refcount加1,表示同时有几个变量在用到它
$a = 'hello world';
$b = $a;
unset($a);
$a = 'hello world';
$b = $a;
$a = 1;
$a = 'hello world';
$b = &$a;
$a = 'hello world';
$b = &$a;
$a = 1;
$a = 'hello world';
$b = $a;
$c = &$a;
垃圾回收
通常,我们写的代码,有两个机会来做到内存的释放:
- 首先,如果一个变量的引用计数变成0(通常是调用了unset),它所对应的zval将会直接释放(可能不是真的删除,只是释放掉符号表与zval的映射关系)
- 其次,在脚本执行完后PHP会释放掉。
如上所述,在PHP5.3之前,没有专门的垃圾回收,zval内存的释放只是简单地通过引用计数是否为0来进行。通过引用计数为0来销毁变量会有个问题,当数组或者对象有循环引用的情况下,当unset的时候zval refcount并没有变成0,这一部分的内存是没有办法释放掉的,会造成内存泄漏,虽然脚本执行完后PHP会释放,但是当PHP作为守护进程需要长时间运行,或者在运行过程中占用内存比较大的时候,这种机制不能有效地进行垃圾回收,造成内存占用过大。这个时候可以调用gc_collect_cycles
来做到强制回收。
在PHP5.3之后,对垃圾回收有了改进,当引用计数变为0时,直接清除。当引用计数减1以后大于0时,认为其可能是垃圾,把它放入垃圾缓冲区,当缓冲区满了(默认是1w个zval)才统一进行垃圾回收。这里需要注意的是:只有减掉以后引用计数仍然大于0的zval才会被认为是垃圾,所以如果在代码中不调用unset是不会减掉引用计数的(除了变量分离的情况),那自然也就不会进行垃圾回收,只有等脚本结束才会被释放掉。执行垃圾回收的时候,PHP遍历zval的每个元素进行模拟删除,如果zval的引用计数变成0了,证明此zval是个垃圾。