$a = 'apple'; $b = &$a;
上述代码中,我将一个字符串赋值给变量a,而后将a的引用赋值给了变量b。显然,这个时候的内存指向应该是这样的:php
$a -> 'apple' <- $b
a和b指向了同一块内存区域(变量容器 zval ),咱们经过 var_dump($a, $b)
获得 string(5) "apple" string(5) "apple"
,这是咱们预期的结果。html
假如我想将 'apple'
这个字符串从内存中释放掉。我是这么作的:web
unset($a);
可是经过再次打印 $a
$b
两变量的信息,我获得了这样的结果:Notice: Undefined variable: a
和 string(5) "apple"
。奇怪,$a
$b
指向同一个变量容器,又明明将$a
释放了,为何$b仍是'apple'
。算法
实际上是这样的,unset()
只是将一个变量符号a
(指针)销毁了,并无释放掉那个变量容器,因此执行完操做以后,内存指向只是变成了这样:segmentfault
'apple' <- $b
引用计数 (reference count)是每一个变量容器中都会存放的一条信息,它表示当前变量容器正被多少个变量符号所引用。数组
正如以前的例子,unset()并无释放变量所指向的变量容器,而只是将变量符号销毁了。同时,将变量容器中的 引用计数 减1,当引用计数为0时,也就是说当变量容器不被任何变量引用时,便会触发php的垃圾回收(错误) ,它便会被释放(正确)。swoole
更正上述的一个小错误: 这种单纯的引用计数方式是 php 5.2 以前的内存管理机制,称不上是垃圾回收机制,垃圾回收机制是 php 5.3 才引入的,垃圾回收机制为的是解决这种单纯的引用计数内存管理机制的缺陷(即 循环引用致使的内存泄漏,下文会进行讲解)app
回到正题,咱们用代码来验证一下先前的结论:函数
$a = 'apple'; $b = &$a; $before = memory_get_usage(); unset($a); $after = memory_get_usage(); var_dump($before - $after); // 结果为int(0),变量容器的引用计数为1,没有释放
$a = 'apple'; $b = &$a; $before = memory_get_usage(); unset($a, $b); $after = memory_get_usage(); var_dump($before - $after); // 结果为int(24),变量容器的引用计数为0,获得释放
那要怎样作才能真正释放掉 'apple'
所占用的内存呢?spa
利用上述方法,咱们能够在 unset($a)
以后再 unset($b)
,将变量容器的全部引用都销毁,引用计数减为0了,天然就被释放掉了。
固然,还有更直接的方法:
$a = null;
直接赋值 null
会将 $a
所指向的内存区域置空,并将引用计数归零,内存便被释放。
对于通常的web程序来讲(fpm模式下),php的执行是单线程同步阻塞型的,当脚本执行结束以后,脚本内使用的全部内存都会被释放。那么,咱们手动去释放内存到底有意义吗?
其实关于这个问题,早有解答,推荐你们看一下鸟哥 @laruence 2012年发表的一篇文章:
请手动释放你的资源(Please release resources manually)
如今咱们来说讲以前提到的引用计数内存管理机制的缺陷。
当一个变量容器的引用计数为0时,php会进行垃圾回收。可是,你可想过,有一种状况会致使一个变量容器的引用计数永远不会被减为0,举个例子:
$a = ['one']; $a[] = &$a;
咱们看到,$a
数组第二个元素就是它自己。那么,存放数组的这个变量容器的引用计数为2,一个引用是变量a
,另外一个引用是这个数组的第二个元素 - 索引1
。
那么,若是这时咱们 unset($a)
,存放数组的变量容器的引用计数会减1,但还有1个引用,就是数组的元素 1
,如今引用结构变成了这样:
因为变量容器的引用计数没有变为0,因此不能被释放,并且这时又没有外部其余变量符号引用它,用户也没有办法去清除这个结构,这时它就会一直驻留在内存之中。
因此若是代码中存在大量的这种结构和操做,最终会致使内存损耗甚至泄漏。这就是 循环引用 带来的内存没法释放的问题。
庆幸的是,fpm模式下,当请求的脚本执行结束,php会释放全部脚本中使用到的内存,包括这个结构。可是,若是是守护进程下的php程序呢?好比swoole。这个php须要解决的急迫问题(已经解决,见下文)。
传统上,像之前的 php 用到的引用计数内存机制,没法处理循环引用的内存泄漏。然而 5.3.0 PHP 使用文章 » 引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems) 中的同步算法,解决了这个内存泄漏问题,这种算法就是PHP的垃圾回收机制。
具体算法的实现和流程有些许复杂,请阅读官方文档,这里再也不赘述,另附上几个算法流程讲解的文章连接,讲得比较直白:
http://php.net/manual/zh/feat... 官方文档
http://www.cnblogs.com/leoo2s...
https://blog.csdn.net/phpkern...
最后,仍是引用鸟哥文章的这两段来讲明问题:
在PHP5.2之前, PHP使用引用计数(Reference count)来作资源管理, 当一个zval的引用计数为0的时候, 它就会被释放. 虽然存在循环引用(Cycle reference), 但这样的设计对于开发Web脚原本说, 没什么问题, 由于Web脚本的特色和它追求的目标就是执行时间短, 不会长期运行. 对于循环引用形成的资源泄露, 会在请求结束时释放掉. 也就是说, 请求结束时释放资源, 是一种补救措施(backup).然而, 随着PHP被愈来愈多的人使用, 就有不少人在一些后台脚本使用PHP, 这些脚本的特色是长期运行, 若是存在循环引用, 致使引用计数没法及时释放不用的资源, 则这个脚本最终会内存耗尽退出.
因此在PHP5.3之后, 咱们引入了GC, 也就是说, 咱们引入GC是为了解决用户没法解决的问题.