PHP垃圾回收机制简单说明


Posted in PHP onJuly 22, 2010

虽然自己也是PHP的学习者,但之前还真没怎么了解PHP内部的垃圾回收流程,只是在我们的代码中用了unset,null,mysql_close,__destruct等等一些函数去释放对象防止内存溢出而已,所以上网GG下,找到了以下一些说明,作下记录“PHP可以自动进行内存管理,清除不再需要的对象。PHP使用了引用计数(reference counting)这种单纯的垃圾回收(garbage collection)机制。每个对象都内含一个引用计数器,每个reference连接到对象,计数器加1。当reference离开生存空间或被设为NULL,计数器减1。当某个对象的引用计数器为零时,PHP知道你将不再需要使用这个对象,释放其所占的内存空间。”

众所周知, PHP 引擎本身是用 C 写的,提到 C 不能不提的就是 GC(垃圾回收).通过 PHP 手册 我们了解到, PHP 引擎会自动进行 GC 动作.那么我们不禁要问,到底它是怎么回收的, & 引用操作是不是指针, unset() 了一个变量时它是不是真的被回收了呢?这些看似手册有提及的问题,如果仔细分析会发现,远没有那么简单泛泛.也许有人会跳出来说:看 PHP 源码不就知道了.是的,等你通读了 PHP 源码后这个问题肯定不在话下了,然本篇要仅从 PHP 本身来分析这些看似平常却被忽视的小细节,当然了,其中难免水平所限,有所疏漏,热烈欢迎广大 phper 来共同讨论.

首先咱先看到例子,最简单不过的执行流程了:
Example 1: gc.php
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

echo $b ." ";
?>

不用说 % php -f gc.php 输出结果非常明了:
hy0kl% php -f gc.php
I am test.

好,下一个:
Example 2:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

$b = 'I will change?';

echo $a ." ";
echo $b ." ";
?>
执行结果依然很明显:
hy0kl% php -f gc.php
I will change?
I will change?

君请看:
Example 3:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

unset($a);

echo $a ." ";
echo $b ." ";
?>
是不是得想一下下呢?
hy0kl% php -f gc.php
Notice: Undefined variable: a in /usr/local/www/apache22/data/test/gc.php on line 8
I am test.
有点犯迷糊了吗?

君再看:
Example 4:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

unset($b);

echo $a ." ";
echo $b ." ";
?>
其实如果 Example 3 理解了,这个与之异曲同工.
hy0kl% php -f gc.php
I am test.
Notice: Undefined variable: b in /usr/local/www/apache22/data/test/gc.php on line 9

君且看:
Example 5:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

$a = null;

echo '$a = '. $a ." ";
echo '$b = '. $b ." ";
?>
猛的第一感觉是什么样的?
hy0kl% php -f gc.php
$a =
$b =
没错,这就是输出结果,对 PHP GC 已有深入理解的 phper 不会觉得有什么奇怪,说实话,当我第一次运行这段代码时很意外,却让我对 PHP GC 有更深刻的理解了.那么下面与之同工的例子自然好理解了.

Example 6:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

$b = null;

echo '$a = '. $a ." ";
echo '$b = '. $b ." ";
?>

OK,如果上面的例子的结果对看官来说无任何细节可言,那您可关闭本窗口了,欢迎有空再来!

下面我们来详细分析 GC 与引用.
1. 所有例子中,创建了一个变量,这个过程通俗一点讲:是在内存中开辟了一块空间,在里面存放了一个字符串 I am test. . PHP 内部有个符号表,用来记录各块内存引用计数,那么此时会将这块内存的引用计数 加 1,并且用一个名为 $a 的标签(变量)指向这块内存,方便依标签名来操作内存.

2. 对变量 $a 进行 & 操作,我的理解是找到 $a 所指向的内存,并为 $b 建立同样的一引用指向,并将存放字符串 I am test. 的内存块在符号表中引用计数 加 1.换言之,我们的脚本执行到这一行的时候,存放字符串 I am test. 的那块内存被引用了两次.这里要强调的是, & 操作是建立了引用指向,而不是指针, PHP 没有指针的概念!同时有人提出说类似于 UNIX 的文件软链接.可以在一定程度上这么理解: 存放字符 I am test. 的那块内存是我们的一个真实的文件,而变量 $a 与 $b 是针对真实文件建立的软链接,但它们指向的是同一个真实文件. So, 我们看到,在 Example 2 中给 $b 赋值的同时, $a 的值也跟着变化了.与通过某一软链操作了文件类似.

3. 在 Example 3 与 4 中,进行了 unset() 操作.根据实际的执行结果,可以看出: unset() 只是断开这个变量对它原先指向的内存的引用,使变量本身成为没有定义过空引用,所在调用时发出了 Notice ,并且使那块内存在符号表中引用计数 减 1,并没有影响到其他指向这块内存的变量.换言之,只有当一块内存在符号表中的引用计数为 0 时, PHP 引擎才会将这块内存回收.
PHP 手册
4.0.0 unset() became an expression. (In PHP 3, unset() would always return 1).
这意味着什么?
看看下面的代码与其结果:
<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

unset($a);
unset($a);
unset($a);

echo '$a = '. $a ." ";
echo '$b = '. $b ." ";
?>
hy0kl% php -f gc.php

Notice: Undefined variable: a in /usr/local/www/apache22/data/test/gc.php on line 10
$a =
$b = I am test.
第一次 unset() 的操作已经断开了指向,所以后继的操作不会对符号表的任何内存的引用记数造成影响了.

4. 通过 Example 5 & 6 可以明确无误得出: 赋值 null 操作是相当猛的,它会直接将变量所指向的内存在符号号中的引用计数置 0, 那这块内存自然被引擎回收了,至于何时被再次利用不得而知,有可能马上被用作存储别的信息,也许再也没有使用过.但是无论如何,原来所有指向那块内存变量都将无法再操作被回收的内存了,任何试图调用它的变量都将返回 null.

<?php
error_reporting(E_ALL);
$a = 'I am test.';
$b = & $a;

$b = null;

echo '$a = '. $a ." ";
echo '$b = '. $b ." ";

if (null === $a)
{
echo '$a is null.';
} else
{
echo 'The type of $a is unknown.';
}
?>
hy0kl% php -f gc.php
$a =
$b =
$a is null.

综上所述,充分说明了为什么我们在看开源产品源码的时候,常看到一些比较大的临时变量,或使用完不再调用的重用信息都会被集中或显示的赋值为 null 了.它相当于 UNIX 中直接将真实文件干掉了,所有指向它的软链接自然成了空链了.

PHP 相关文章推荐
Windows下IIS6/Apache2.2.4+MySQL5.2+PHP5.2.1安装配置方法
May 03 PHP
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
Aug 26 PHP
PHP执行linux系统命令的常用函数使用说明
Apr 27 PHP
PHP 第二节 数据类型之数组
Apr 28 PHP
PHP设计模式之责任链模式的深入解析
Jun 13 PHP
php中的比较运算符详解
Oct 28 PHP
浅谈使用 PHP 进行手机 APP 开发(API 接口开发)
Aug 11 PHP
Smarty变量调节器失效的解决办法
Aug 20 PHP
PHP与MYSQL中UTF8 中文排序示例代码
Oct 23 PHP
PHP中加速、缓存扩展的区别和作用详解(eAccelerator、memcached、xcache、APC )
Jul 09 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
Aug 01 PHP
PHP crypt()函数的用法讲解
Feb 15 PHP
PHP多线程抓取网页实现代码
Jul 22 #PHP
php上传文件的增强函数
Jul 21 #PHP
php 模拟POST|GET操作实现代码
Jul 20 #PHP
UCenter中的一个可逆加密函数authcode函数代码
Jul 20 #PHP
PHP连接SQLServer2005 的问题解决方法
Jul 19 #PHP
在Windows系统上安装PHP运行环境文字教程
Jul 19 #PHP
ajax实现无刷新分页(php)
Jul 18 #PHP
You might like
php实现分页工具类分享
2014/01/09 PHP
php检测图片主要颜色的方法
2015/07/01 PHP
yii2实现 &quot;上一篇,下一篇&quot; 功能的代码实例
2017/02/04 PHP
Zend Framework框架中实现Ajax的方法示例
2017/06/27 PHP
PHP实现一维数组与二维数组去重功能示例
2018/05/24 PHP
PHP5.5新特性之yield理解与用法实例分析
2019/01/11 PHP
ASP.NET中使用后端代码注册脚本 生成JQUERY-EASYUI的界面错位的解决方法
2010/06/12 Javascript
Jquery为单选框checkbox绑定单击click事件
2012/12/18 Javascript
JS限制Textarea文本域字符个数的具体实现
2013/08/02 Javascript
不同Jquery版本引发的问题解决
2013/10/14 Javascript
快速解决jquery之get缓存问题的最简单方法介绍
2013/12/19 Javascript
JavaScript中读取和保存文件实例
2014/05/08 Javascript
Firefox下无法正常显示年份的解决方法
2014/09/04 Javascript
JS小游戏之象棋暗棋源码详解
2014/09/25 Javascript
CentOS环境中MySQL修改root密码方法
2018/01/07 Javascript
浅谈mvvm-simple双向绑定简单实现
2018/04/18 Javascript
JointJS流程图的绘制方法
2018/12/03 Javascript
vue中的inject学习教程
2019/04/24 Javascript
基于layui框架响应式布局的一些使用详解
2019/09/16 Javascript
浅析Python多线程下的变量问题
2015/04/28 Python
机器学习的框架偏向于Python的13个原因
2017/12/07 Python
matplotlib在python上绘制3D散点图实例详解
2017/12/09 Python
python批量修改图片大小的方法
2018/07/24 Python
详解python读取image
2019/04/03 Python
Python多线程threading模块用法实例分析
2019/05/22 Python
关于tf.nn.dynamic_rnn返回值详解
2020/01/20 Python
在Mac中PyCharm配置python Anaconda环境过程图解
2020/03/11 Python
python matplotlib包图像配色方案分享
2020/03/14 Python
Python requests.post方法中data与json参数区别详解
2020/04/30 Python
Keras实现支持masking的Flatten层代码
2020/06/16 Python
keras使用Sequence类调用大规模数据集进行训练的实现
2020/06/22 Python
CSS3实现可关闭的下拉手风琴菜单效果
2015/08/31 HTML / CSS
html5 canvas绘制矩形和圆形的实例代码
2016/06/16 HTML / CSS
承诺书格式
2014/06/03 职场文书
2015年房产销售工作总结范文
2015/05/22 职场文书
北京青年观后感
2015/06/15 职场文书