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 ss7.5的数据调用 (笔记)
Mar 08 PHP
通过缓存数据库结果提高PHP性能的原理介绍
Sep 05 PHP
php获取用户IPv4或IPv6地址的代码
Nov 15 PHP
PHP实现取得HTTP请求的原文
Aug 18 PHP
PHP中的Streams详细介绍
Nov 12 PHP
WordPress分页伪静态加html后缀
Jun 08 PHP
php实现常见图片格式的水印和缩略图制作(面向对象)
Jun 15 PHP
php获取POST数据的三种方法实例详解
Dec 20 PHP
Laravel接收前端ajax传来的数据的实例代码
Jul 20 PHP
在 Laravel 中 “规范” 的开发短信验证码发送功能
Oct 26 PHP
php解决crontab定时任务不能写入文件问题的方法分析
Sep 16 PHP
php使用yield对性能提升的测试实例分析
Sep 19 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函数之子字符串替换&amp;#65279; str_replace
2011/03/23 PHP
PHP获取音频文件的相关信息
2015/06/22 PHP
ThinkPHP框架整合微信支付之Native 扫码支付模式二图文详解
2019/04/09 PHP
TP5(thinkPHP5框架)基于bootstrap实现的单图上传插件用法示例
2019/05/29 PHP
Aster vs Newbee BO3 第二场2.18
2021/03/10 DOTA
jQuery获取注册信息并提示实现代码
2013/04/21 Javascript
用JavaScript计算在UTF-8下存储字符串占用字节数
2013/08/08 Javascript
JavaScript中用getDate()方法返回指定日期的教程
2015/06/09 Javascript
javascript框架设计之类工厂
2015/06/23 Javascript
有关easyui-layout中的收缩层无法显示标题的解决办法
2016/05/10 Javascript
javascript学习之json入门
2016/12/22 Javascript
js+html制作简单验证码
2017/02/16 Javascript
Bootstrap页面标题Page Header的实现方法
2017/03/22 Javascript
jquery实现全选、全不选以及单选功能
2017/03/23 jQuery
Vue利用路由钩子token过期后跳转到登录页的实例
2017/10/26 Javascript
jQuery删除/清空指定元素的所有子节点实例代码
2019/07/04 jQuery
跟老齐学Python之玩转字符串(3)
2014/09/14 Python
解决pip install的时候报错timed out的问题
2018/06/12 Python
python事件驱动event实现详解
2018/11/21 Python
Python实现合并两个有序链表的方法示例
2019/01/31 Python
Python 中Django安装和使用教程详解
2019/07/03 Python
mac使用python识别图形验证码功能
2020/01/10 Python
Python装饰器实现方法及应用场景详解
2020/03/26 Python
详解Selenium 元素定位和WebDriver常用方法
2020/12/04 Python
利用python如何实现猫捉老鼠小游戏
2020/12/04 Python
HTML5学习笔记之html5与传统html区别
2016/01/06 HTML / CSS
巧用HTML5给按钮背景设计不同的动画简单实例
2016/08/09 HTML / CSS
纽约州一群才华横溢的金匠制作而成:Hearth Jewelry
2019/03/22 全球购物
Optimalprint加拿大:在线打印服务
2020/04/03 全球购物
What is EJB
2016/07/22 面试题
找工作最新求职信
2013/12/22 职场文书
入党积极分子思想汇报范文
2014/01/05 职场文书
情侣吵架检讨书
2014/02/05 职场文书
四风查摆问题自查报告
2014/10/10 职场文书
迁徙的鸟观后感
2015/06/09 职场文书
灵魂歌王观后感
2015/06/17 职场文书