原文:PHP的垃圾回收机制详解

最近由于使用php编写了一个脚本,模拟实现了一个守护进程,因此需要深入理解php中的垃圾回收机制。本文参考了PHP手册。

在理解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

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

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

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

上面描述的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

 

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

object(Test)[1]
public 'a' => (refcount=2, is_ref=0),

int

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

int

 

可以看出,数组用了比数组长度多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.1415962654';
} for ( $i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
} echo memory_get_peak_usage(), "\n";
?>

  

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

通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。



PHP的垃圾回收机制详解的更多相关文章

  1. JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...

  2. java面试题之----JVM架构和GC垃圾回收机制详解

    JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...

  3. GC垃圾回收机制详解

    JVM堆相关知识    为什么先说JVM堆?  JVM的堆是Java对象的活动空间,程序中的类的对象从中分配空间,其存储着正在运行着的应用程序用到的所有对象.这些对象的建立方式就是那些new一类的操作 ...

  4. Python垃圾回收机制详解

    一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题. 在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. #e ...

  5. JVM的内存区域划分以及垃圾回收机制详解

    在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉.因为JVM中有垃圾自动回收机制.在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以 ...

  6. php5.3新垃圾回收机制详解

    php的垃圾回收机制主要参考了http://blog.csdn.net/phpkernel/article/details/5734743 这文章. 变量对应的值,比如 $a="abc&qu ...

  7. Python垃圾回收机制详解转自--Kevin Lu

    一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题. 在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. #e ...

  8. PHP 垃圾回收机制详解

    前言:之前对PHP的GC只是了解了个大概,这次详细了解下PHP的垃圾回收机制(GC). 介于网上大部分都是PHP5.X的GC,虽然 php5 到 php7 GC部分做出的改动较小,但我觉得还是一起写下 ...

  9. Java垃圾回收机制详解和调优

    gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集 ...

随机推荐

  1. 开源Math.NET基础数学类库使用(13)C#实现其他随机数生成器

    原文:[原创]开源Math.NET基础数学类库使用(13)C#实现其他随机数生成器                本博客所有文章分类的总目录:http://www.cnblogs.com/asxiny ...

  2. HDU 3715 Go Deeper(2-sat)

    HDU 3715 Go Deeper 题目链接 题意:依据题意那个函数,构造x数组.问最大能递归层数 思路:转化为2-sat问题,因为x仅仅能是0.1,c仅仅能是0,1.2那么问题就好办了,对于0, ...

  3. crm2011i创建nt类型字段

    using System;     using Microsoft.Xrm.Sdk;     using Microsoft.Xrm.Sdk.Messages;     using Microsoft ...

  4. [LeetCode] Combinations [38]

    称号 Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exa ...

  5. 《TCP/IP作品详细解释2:达到》注意事项--IP地址

    1.接口和地址 如下面的图全部本文中讨论的接口和地址的结构看一个示例配置: 上图中显示了我们三个接口样例:以太网接口,SLIP接口和环回接口. 它们都有一个链路层地址作为地址列表中的第一个结点. 显示 ...

  6. Java得到年在一个季度的错误的第一天

    1.错误叙述性说明 Exception in thread "main" java.lang.IllegalArgumentException: Cannot format giv ...

  7. Maven工程引入jar包(转)

    Maven项目引入jar包的方法,希望能帮助有需要的朋友们 法一.手动导入:项目右键—>Build Path—>Configure Build Path—>选中Libraries—& ...

  8. iOS开发- 隐藏状态栏(电池栏)

    分为两种情况: 1. 想要隐藏某个视图的状态栏, 比方说, 从界面A, push 到界面B的时候, 界面A原本显示状态栏, 然而我们须要界面B不显示状态栏. 这时候, 能够这样做: 在B中实现: - ...

  9. HTML与XHTML差额

    实际上.XHTML 与 HTML 4.01 标准没有太多的不同. 它们最基本的不同.举例说明例如以下: 1.XHTML 元素必须被正确地嵌套. 错误:<p><span>this ...

  10. html不常见问题汇总

    写html已经好长一段时间了,也遇到了不少问题,跟大家分享下 form是不可以嵌套的 说明:如果嵌套会有很多问题 但是可以并列 <html> <head> </head& ...