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 相关文章推荐
一个简单的PHP入门源程序
Oct 09 PHP
PHP 高手之路(一)
Oct 09 PHP
PHP文件读写操作之文件写入代码
Jan 13 PHP
php设计模式  Command(命令模式)
Jun 17 PHP
PHP版网站缓存加快打开速度的方法分享
Jun 03 PHP
深入解析php之sphinx
May 15 PHP
destoon实现调用热门关键字的方法
Jul 15 PHP
PHP学习笔记(二):变量详解
Apr 17 PHP
编写PHP脚本过滤用户上传的图片
Jul 03 PHP
PHP使用CURL模拟登录的方法
Jul 08 PHP
PHP receiveMail实现收邮件功能
Apr 25 PHP
一次项目中Thinkphp绕过禁用函数的实战记录
Nov 17 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获取photoshop写入图片文字信息的方法
2015/03/31 PHP
实例讲解PHP验证邮箱是否合格
2019/01/28 PHP
ThinkPHP5&amp;5.1实现验证码的生成、使用及点击刷新功能示例
2020/02/07 PHP
javascript div 弹出可拖动窗口
2009/02/26 Javascript
JavaScript DOM 编程艺术(第2版)读书笔记(JavaScript的最佳实践)
2013/10/01 Javascript
解决Jquery向页面append新元素之后事件的绑定问题
2015/03/16 Javascript
JS+CSS实现下拉列表框美化效果(3款)
2015/08/15 Javascript
js判断手机访问或者PC的几个例子(常用于手机跳转)
2015/12/15 Javascript
Javascript复制实例详解
2016/01/28 Javascript
分享js粘帖屏幕截图到web页面插件screenshot-paste
2020/08/21 Javascript
ReactNative页面跳转实例代码
2016/09/27 Javascript
bootstrap table表格插件之服务器端分页实例代码
2018/09/12 Javascript
vue实现自定义日期组件功能的实例代码
2018/11/06 Javascript
JS匿名函数内部this指向问题详析
2019/05/10 Javascript
Vue项目实现换肤功能的一种方案分析
2019/08/28 Javascript
vue cli3 配置proxy代理无效的解决
2019/10/30 Javascript
微信小程序swiper实现文字纵向轮播提示效果
2020/01/21 Javascript
解决vue+webpack项目接口跨域出现的问题
2020/08/10 Javascript
jquery实现点击左右按钮切换图片
2021/01/27 jQuery
[01:31]DOTA2上海特级锦标赛 SECRET战队完整宣传片
2016/03/16 DOTA
python 实现在txt指定行追加文本的方法
2018/04/29 Python
解决matplotlib库show()方法不显示图片的问题
2018/05/24 Python
Flask框架Jinjia模板常用语法总结
2018/07/19 Python
Pytorch 实现自定义参数层的例子
2019/08/17 Python
Python关键字及可变参数*args,**kw原理解析
2020/04/04 Python
Django3中的自定义用户模型实例详解
2020/08/23 Python
HTML5实现移动端复制功能
2018/04/19 HTML / CSS
美国南加州的原创极限运动潮牌:Vans(范斯)
2016/08/05 全球购物
美国大码时尚女装购物网站:ELOQUII
2017/12/28 全球购物
2014年元旦活动方案
2014/02/15 职场文书
2014年教师节国旗下讲话稿
2014/09/10 职场文书
党员教师个人对照检查材料范文
2014/09/25 职场文书
2014年党员个人剖析材料
2014/10/08 职场文书
对外汉语专业大学生职业生涯规划书
2014/10/11 职场文书
新郎接新娘保证书
2015/05/08 职场文书
小学教学工作总结2015
2015/05/13 职场文书