博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHP的垃圾回收机制详解
阅读量:4351 次
发布时间:2019-06-07

本文共 3735 字,大约阅读时间需要 12 分钟。

最近由于使用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.  如果给一个变量&赋值,之前 = 赋值的变量会分配空间。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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存储的是标量,那复合类型的数组是如何存储的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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

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

1
2
3
4
5
<?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手册中给的一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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中内存的占用情况,如下图:

针对下面这个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。

转载于:https://www.cnblogs.com/573734817pc/p/10083344.html

你可能感兴趣的文章
CSS2-3常见的demo列子总结一
查看>>
第522篇--DataTable to Excel C#
查看>>
C++\virtual 虚函数、纯虚函数
查看>>
asp.net mvc 4多级area实现技巧
查看>>
Solr
查看>>
MySQL binlog数据库同步技术总结
查看>>
静态方法
查看>>
保护HTTP的安全
查看>>
js 选取子节点时去除非IE浏览器的换行符
查看>>
javascript是一朵奇葩
查看>>
Mycat入门教程
查看>>
关于"无法解析的外部符号"问题的解决
查看>>
【JavaScript】【译】编写高性能JavaScript
查看>>
【随笔】入行必读:互联网行业薪酬等级
查看>>
Android使用开源框架加载图片
查看>>
CLR是怎么加载到内存的?
查看>>
fckeditor
查看>>
backbone.js
查看>>
python类的特殊成员变量
查看>>
sublime text3最新版本注册码(build 3143)
查看>>