参考

PHP的垃圾回收

PHP的内存管理

PHP使用的内存管理算法较为简单,是引用计数机制,当变量赋值、传递时并不会直接硬拷贝,而是增加value的引用数,unset、return等释放变量时再减掉引用数,减掉后如果发现refcount变为0则直接释放value,这是变量的基本gc过程。

内存泄露

但是引用计数法再一种情况下,是存在内存泄露的风险的,那就是自身引用自己,这样数组的引用计数中就有一个来自自身成员,试图释放数组时因为其refcount仍然大于0而得不到释放,而实际上已经没有任何外部引用了,这种变量不可能再被使用,既造成了内存泄露。

垃圾回收机制

注意:垃圾回收机制只会作用于数组和对象两种数据类型,因为只有这两种数据类型才会存在自引用
下面看一个数组循环引用的例子:

  1. $a = [1];
  2. $a[] = &$a;
  3. unset($a);

unset($a) 之前的关系:
image.png
unset($a) 之后:
image.png
理论上再 unset($a) 之后,这个数组已经是垃圾了,但是因为存在内部引用, $arefcount = 1 ,造成了垃圾无法被清理掉。
垃圾回收期就是要处理这种情况,这里明确两个准则:

  1. 如果一个变量value的refcount减少到0, 那么此value可以被释放掉,不属于垃圾
  2. 如果一个变量value的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾

回收过程

如果当变量的refcount减少后大于0 ,PHP并不会立即进行对这个变量进行垃圾鉴定,而是放入一个缓冲buffer中,等这个buffer满了以后(10000个值)再统一进行处理,加入buffer的是变量zend_value的

  1. typedef struct _zend_refcounted_h {
  2. uint32_t refcount; //记录zend_value的引用数
  3. union {
  4. struct {
  5. zend_uchar type, //zend_value的类型,与zval.u1.type一致
  6. zend_uchar flags,
  7. uint16_t gc_info //GC信息,垃圾回收的过程会用到
  8. } v;
  9. uint32_t type_info;
  10. } u;
  11. } zend_refcounted_h;

变量被加入到buffer中后,为了防止重复加入,变量加入后会把 zend_refcounted_h.gc_info 设置为 GC_PURPLE ,标为紫色。
垃圾缓存区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓存区,再对当前变量的所有成员进行遍历,然后把成员的refcount减1(如果成员还包含子成员则也进行递归遍历,其实就是深度优先的遍历),最后再检查当前变量的引用,如果减为了0则为垃圾。 这个算法的原理很简单,垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,结果如果发现变量本身refcount变为了0则就表明其引用全部来自自身成员