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 相关文章推荐
第五节 克隆 [5]
Oct 09 PHP
福利彩票幸运号码自动生成器
Oct 09 PHP
Mysql中分页查询的两个解决方法比较
May 02 PHP
关于使用key/value数据库redis和TTSERVER的心得体会
Jun 28 PHP
PHP实现对站点内容外部链接的过滤方法
Sep 10 PHP
PHP字符串比较函数strcmp()和strcasecmp()使用总结
Nov 19 PHP
Smarty最简单实现列表奇偶变色的方法
Jul 01 PHP
Joomla简单判断用户是否登录的方法
May 04 PHP
PHP实现的登录页面信息提示功能示例
Jul 24 PHP
thinkPHP通用控制器实现方法示例
Nov 23 PHP
PHP实现的多维数组排序算法分析
Feb 10 PHP
使用PHP访问RabbitMQ消息队列的方法示例
Jun 06 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
第六节--访问属性和方法
2006/11/16 PHP
php多种形式发送邮件(mail qmail邮件系统 phpmailer类)
2014/01/22 PHP
PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
2014/06/23 PHP
PHP防止刷新重复提交页面的示例代码
2015/11/11 PHP
万能的php分页类
2017/07/06 PHP
jQuery学习笔记之Helloworld
2010/12/22 Javascript
Node.js入门教程:在windows和Linux上安装配置Node.js图文教程
2014/08/14 Javascript
jquery+json实现数据二级联动的方法
2015/11/28 Javascript
完美实现八种js焦点轮播图(上篇)
2016/07/18 Javascript
Javascript之面向对象--封装
2016/12/02 Javascript
React-Native中props具体使用详解
2017/09/04 Javascript
基于es6三点运算符的使用方法(实例讲解)
2017/10/12 Javascript
详解Nodejs mongoose
2018/06/10 NodeJs
微信小程序开发背景图显示功能
2018/08/08 Javascript
如何实现js拖拽效果及原理解析
2020/05/08 Javascript
python用来获得图片exif信息的库实例分析
2015/03/16 Python
Python中函数的参数传递与可变长参数介绍
2015/06/30 Python
初步剖析C语言编程中的结构体
2016/01/16 Python
Python使用Pickle库实现读写序列操作示例
2018/06/15 Python
Django中使用Whoosh进行全文检索的方法
2019/03/31 Python
django搭建项目配置环境和创建表过程详解
2019/07/22 Python
Django Form设置文本框为readonly操作
2020/07/03 Python
linux centos 7.x 安装 python3.x 替换 python2.x的过程解析
2020/12/14 Python
python 三种方法提取pdf中的图片
2021/02/07 Python
Html5 APP中监听返回事件处理的方法示例
2018/03/15 HTML / CSS
HTML5 直播疯狂点赞动画实现代码 附源码
2020/04/14 HTML / CSS
美国Curacao百货连锁店网站:iCuracao.com
2019/07/20 全球购物
Java平台和其他软件平台有什么不同
2015/06/05 面试题
高中的自我鉴定
2013/12/16 职场文书
最经典的大学生职业生涯规划范文
2014/03/05 职场文书
工厂门卫的岗位职责
2014/07/27 职场文书
“向国旗敬礼”活动策划方案(4篇)
2014/09/27 职场文书
门市房租房协议书
2014/12/04 职场文书
2015年暑期社会实践总结
2015/07/13 职场文书
Pytest allure 命令行参数的使用
2021/04/18 Python
uwsgi+nginx代理Django无法访问静态资源的解决
2021/05/10 Servers