在理解PHP垃圾回收机制(GC)之前,先了解一下变量的存储。

php中变量存在于一个zval的变量容器中。结构如下:

类型

is_ref

refcount

zval中,除了存储变量的类型和值之外,还有is_ref字段和refcount字段。

is_ref:是个bool值,用来区分变量是否属于引用集合。什么意思呢,你可以这么认为:表示变量是否有一个以上的别名。

refcount:计数器,表示指向这个zval变量容器的变量个数。

两者之间有这么一个默认关系:当refcount值为1时,is_ref的值为false。因为refcount为1,此变量不可能有多个别名,也就不存在引用了。

安装xdebug拓展之后,可以利用xdebug_debug_zval打印出zval容器详情。

这里有一点需要注意,将一个变量 = 赋值给另一个变量时,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。当然,如果unset原变量,新变量直接就使用原变量的zval而不是重新分配。

&引用赋值时,原变量的is_ref 变为1,refcount 加1. 如果给一个变量&赋值,之前 = 赋值的变量会分配空间。

<?php

$a = 1;

xdebug_debug_zval('a');

echo PHP_EOL;

$b = $a;

xdebug_debug_zval('a');

echo PHP_EOL;

$c = &$a;

xdebug_debug_zval('a');

echo PHP_EOL;

xdebug_debug_zval('b');

echo PHP_EOL;

?>

  运行结果如下:

a:(refcount=1, is_ref=0),int 1

a:(refcount=2, is_ref=0),int 1

a:(refcount=2, is_ref=1),int 1

b:(refcount=1, is_ref=0),int 1

上面描述的zval存储的是标量,那复合类型的数组是如何存储的呢?

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );

xdebug_debug_zval( 'a' );

echo PHP_EOL;

class Test{

public $a = 1;

public $b = 2;

function handle(){

echo 'hehe';

}

}

$test = new Test();

xdebug_debug_zval('test');

?>

  运行结果如下:

a:(refcount=1, is_ref=0),

array

'meaning' => (refcount=1, is_ref=0),

string

'life' (length=4)

'number' => (refcount=1, is_ref=0),

int

42

test:(refcount=1, is_ref=0),

object(Test)[1]

public 'a' => (refcount=2, is_ref=0),

int

1

public 'b' => (refcount=2, is_ref=0),

int

2

可以看出,数组用了比数组长度多1个zval存储。对象类似。下面给出了数组的存储形象表示

可以看到:数组分配了三个zval容器:a meaning number

现在看看所谓的环状引用是如何生成的

<?php

$a = array( 'one' );

$a[] =& $a;

xdebug_debug_zval( 'a' );

?>

  运行结果:

a:(refcount=2, is_ref=1),

array

0 => (refcount=1, is_ref=0),

string

'one' (length=3)

1 => (refcount=2, is_ref=1), &array

a 和 1 的zval容器 是一样的。如下:

这样就形成了环状引用。

在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount为0,那么变量的空间可以被释放,否则就不释放,这是一种非常简单的GC实现。

现在unset ($a),那么array的refcount减1变为1.现在无任何变量指向这个zval,而且这个zval的计数器为1,不会回收。

尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php将在请求结束时清除这个数据结构,但是在php清除之前,将耗费不少空间的内存。如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。

如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。在长时间运行的脚本,比如请求基本上不会结束的守护进程时,就会出现问题,内存空间会不断耗费,导致内存不足而崩溃。

PHP5.3中,采用了专门的算法(比较复杂)。,来处理环状引用导致内存泄露的问题。

当一个zval可能为垃圾时,回收算法会把这个zval放入一个内存缓冲区。当缓冲区达到最大临界值时(最大值可以设置),回收算法会循环遍历所有缓冲区中的zval,判断其是否为垃圾,并进行释放处理。或者我们在脚本中使用gc_collect_cycles,强制回收缓冲区中的垃圾。

在php5.3的GC中,针对的垃圾做了如下说明:

1:如果一个zval的refcount增加,那么此zval还在使用,肯定不是垃圾,不会进入缓冲区

2:如果一个zval的refcount减少到0, 那么zval会被立即释放掉,不属于GC要处理的垃圾对象,不会进入缓冲区。

3:如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,将其放入缓冲区。PHP5.3中的GC针对的就是这种zval进行的处理。

开启/关闭垃圾回收机制可以通过修改php配置实现,也可以在程序中使用gc_enable() 和 gc_disable()开启和关闭。

开启垃圾回收机制后,针对内存泄露的情况,可以节省大量的内存空间,但是由于垃圾回收算法运行耗费时间,开启垃圾回收算法会增加脚本的执行时间。

下面是php手册中给的一个脚本

<?php

class Foo

{

public $var = '3.1415962654';

}

$baseMemory = memory_get_usage();

for ( $i = 0; $i <= 100000; $i++ )

{

$a = new Foo;

$a->self = $a;

if ( $i % 500 === 0 )

{

echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";

}

}

?>

  

针对这个脚本,给出了其在php5.2和5.3中内存的占用情况,如下图:

针对下面这个脚本

<?php

class Foo

{

public $var = '3.2881064151';

}

for ( $i = 0; $i <= 1000000; $i++ )

{

$a = new Foo;

$a->self = $a;

}

echo memory_get_peak_usage(), "\n";

?>

  

开启垃圾回收机制,相对于不开启的时候,脚本执行时间增加了7%

程序中使用gc_enable() 和 gc_disable()开启和关闭的更多相关文章

  1. Android中如何监听GPS开启和关闭

    转自 chenming 原文 Android中如何监听GPS开启和关闭   摘要: 本文简单总结了如何监听GPS开关的小技巧 有时需要监听GPS的开关(这种需求并不多见).实现的思路是监听代表 GPS ...

  2. C# 在程序中控制IIS服务或应用程序池关闭重启

    //停止IIS服务 ServiceController sc = new ServiceController("iisadmin"); if(sc.Status=ServiceCo ...

  3. iOS中定时器NSTimer的使用/开启与关闭

      一.只调用一次计时器方法: //不重复,只调用一次.timer运行一次就会自动停止运行 myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5  ...

  4. Centos中iptables和firewall防火墙开启、关闭、查看状态、基本设置等(转)

    iptables防火墙 1.基本操作 # 查看防火墙状态 service iptables status   # 停止防火墙 service iptables stop   # 启动防火墙 servi ...

  5. MFC如何在有界面的应用程序中开启控制台窗口

    在有界面的应用程序中开启控制台窗口有时候非常有用,尤其是在调试多线程应用程序中,由于通过断点的方式调试程序时会导致线程挂起从而导致各种难于预料的结果.这时候就可以通过开启控制台窗口往窗口输出信息来查看 ...

  6. Python程序中的进程操作-开启多进程(multiprocess.process)

    目录 一.multiprocess模块 二.multiprocess.process模块 三.process模块介绍 3.1 方法介绍 3.2 属性介绍 3.3 在windows中使用process模 ...

  7. Python程序中的进程操作--—--开启多进程

    Python程序中的进程操作-----开启多进程 之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程.所有的进程都是通过它的父进程来创 ...

  8. 转:如何在32位程序中突破地址空间4G的限制

    //如何在32位程序中突破地址空间4G的限制 //首先要获得内存中锁定页的权限 #define _WIN32_WINNT 0x0501 //xp系统 #include <windows.h> ...

  9. [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的连接恢复和命令拦截

    这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第四篇:MVC程序中实体框架的连接恢复和 ...

随机推荐

  1. 使用php递归计算目录大小

    统计一个目录大小,因为不知道目录中子目录的深度,所以for循环很难实现,但是用递归调用很容易实现,只要统计出一个目录中所有文件的大小,那么每一次调用就可以了,随便建了个目录,建立一些文件,方法代码如下 ...

  2. MongoDB副本集学习(一):概述和环境搭建

    MongoDB副本集概述 以下图片摘自MongoDB官方文档:http://docs.mongodb.org/manual/core/replication-introduction/ Primary ...

  3. (九)STM32之AFIO

    也许你以为IO和AFIO是很简单的,事实上有几个误区可能很多人都没注意过,当你只用现成的开发板来学习的时候,别人已经帮你做好了资源分配,所有的外设功能学习都是照着别人给你的例程去做的,这才没让你觉得奇 ...

  4. unsatisfied类型的异常

    一般为libs包下arm64-v8a,armeabi,armeabi-v7a,mips,mips64,x86,x86_64等文件夹下的.so文件丢失. 一般情况,armeabi下的so文件需要拷贝一份 ...

  5. C++ STL算法系列4---unique , unique_copy函数

     一.unique函数 类属性算法unique的作用是从输入序列中“删除”所有相邻的重复元素. 该算法删除相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器(容器的长度没变,只是元素顺序 ...

  6. CodeIgniter - 数据库的增删改查

    数据库操作无非是CRUD,用非装逼的语言来说就是增删改查.也许这一节会讲的很泛泛,或者很多人看不懂,没关系,大致的看看,知道是这么回事就好,继续往后看,后面会讲实例,这些枯燥而又抽象的东西可以先跳过, ...

  7. Log4Net异常日志记录在asp.net mvc3.0的应用(转载)

    这篇博客写的很好:http://www.cnblogs.com/qianlifeng/archive/2011/04/22/2024856.html 前言 log4net是.Net下一个非常优秀的开源 ...

  8. PHP 5.4中的traits特性

    Trait 是 PHP5. 中的新特性,是 PHP 多重继承的一种解决方案.例如,需要同时继承两个 Abstract Class, 这将会是件很麻烦的事情,Trait 就是为了解决这个问题. 简单使用 ...

  9. PHP站内搜索:多关键字、加亮显示

    一.SQL语句中的模糊查找        主要通过LIKE(不区分大小写)关键字实现模糊查找.LIKE条件一般用在指定搜索某字段的时候, 通过"%"或者" _" ...

  10. 几个 Context 上下文的区别

    转自:http://www.blogjava.net/fancydeepin/archive/2013/03/31/java-ee-context.html 在 java 中, 常见的 Context ...