垃圾回收

简称GC。顾名思义,就是废物利用的意思。
说垃圾回收机制之前,先接触一下内存泄露。

内存泄露

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

C语言的垃圾回收机制

如果用过C语言,那么申请内存的方式是malloc或者是calloc,然后你用完这个内存后,一定不要忘了用free函数去释放掉,这就是手动垃圾回收,一般都是使用这种方式。

PHP的自动垃圾回收机制

我们都知道PHP是C语言实现的。你想想如何用C语言实现对一个变量的统计及释放?C语言是如何实现一个变量,从声明开始到没人使用了,就把这个变量所占的内存释放掉(被垃圾回收)。

PHP进行内存管理的核心算法一共两项:
一是引用计数,二是写时拷贝

声明一个PHP变量的时候,C语言就在底层生成了一个叫做zval的struct,如下:

zval {
    string "a" // 变量名为a
    value zend_value // 变量的值,联合体
    type string // 变量是字符串类型
}

zval struct结构体

  1. PHP $a 的变量名
  2. PHP $a 的变量类型
  3. PHP 变量 $a 的zend_value联合体
    如果给变量赋值了,比如"hello world",那么C语言就在底层再生成一个叫做zend_value的union
zend_value {
    string "hello world" // 值内容
    refcount 1 // 引用计数
}

zend_value联合体

  1. 保存PHP $a 的变量值
  2. 记录PHP $a 变量的引用计数

何为引用计数?

$a = "hello world";
echo xdebug_debug_zval('a'); // refcount=1
$b = $a; // $b引用变量$a
echo xdebug_debug_zval('a'); // refcount=2
$c = $a; // $c引用变量$a
echo xdebug_debug_zval('a'); // refcount=3
unset($c); // 删除了$c的引用
echo xdebug_debug_zval('a'); // refcount=2

何为拷贝复制?

$a = 'hello';
$b = $a; // $a赋值给$b的时候,并没有复制值
echo xdebug_debug_zval('a'); // $a的引用计数为2
$a = 'world'; // 当修改$a的值的时候,不得己进行复制,避免牵扯到$b
echo xdebug_debug_zval('a'); // $a的引用计数为1

垃圾回收机制:

当一个zval在被unset时,或者从一个函数中运行完毕出来(局部变量)等很多地方,都会产生zval与zend_value发生断开的行为,这个时候zend引擎需要检测的就是zend_value的refcount是否为0,如果为0则直接KO free空出内容来。如果zend_value的refcount不为0,这个value就不能被释放,但是也不代表这个zend_value是清白的,因为此zend_value依然可能是垃圾。

  1. 当php变量的refcount=0时,变量$a就会被垃圾回收
  2. 当php变量的refcount>0时,变量$a也可能被认为是垃圾

什么情况会导致zend_value的refcount不为0,但是zend_value确是垃圾

$arr = [1];
$arr[] = &$arr;
unset($arr);

这种情况下,zend_value不会释放,但也不能放过它,不然会产生内存泄露,所以这会儿zend_value会被扔到一个叫做垃圾回收堆中,然后zend引擎会依次对垃圾回收堆中的这些zend_value进行二次检测,检测是不是由于上述两种情况造成的refcount为1但是自身却没有人在使用了,一旦确认是上述两种情况造成的,那么就会将zend_value彻底抹掉释放内存。

垃圾回收发生在什么时候

  1. FPM运行完毕后,一定会GC
  2. 运行过程中也会GC,内存都是即用即释放的