page contents

PHP的垃圾回收机制

php5和php7的垃圾回收机制都是利用引用计数


attachments-2020-04-HUknrWsU5e9e8bbf53a34.png

一、原理


php5和php7的垃圾回收机制都是利用引用计数。


二、php5和php7不同点


1、PHP5标量数据类型会计数,PHP7标量数据类型不再计数,不需要单独分配内存。

2、PHP7的zval 需要的内存不再是单独从堆上分配,不再自己存储引用计数。

3、PHP7的复杂数据类型(比如数组和对象)的引用计数由其自身来存储。


三、变量在zval的变量容器中结构


v2-8f18e9b97b4ef9b5ab793b1a50f46326_720w.png

zval中,除了存储变量的类型和值之外,还有is_ref字段和refcount字段
    1、is_ref:是个bool值,用来区分变量是否属于引用集合。
    2、refcount:计数器,表示指向这个zval变量容器的变量个数。 


四、PHP5.3标量在zval容器例子


注意:

php5.3中将一个变量 = 赋值给另一个变量时,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount加1。

只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。


当然,如果unset原变量,新变量直接就使用原变量的zval而不是重新分配。

&引用赋值时,原变量的is_ref 加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


五、PHP7.X 标量在zval容器例子


<?php

$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');

结果如下:可以看到标量(布尔,字符串,整形,浮点型)不再计数了

v2-7f44154545a219edbcc9d17116855ca8_720w.jpg


六、PHP5.3复合类型数组和对象在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');

结果如下:可以看出,数组用了比数组长度多1个zval存储。数组分配了三个zval容器:a   meaning  number
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

v2-9c13c9259a50f918cb56663751698616_720w.jpg



七、PHP7.X复合类型数组和对象在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=2,后经测试发现数组的refcount都是从2开始的


v2-bce30a98a9b5192d487ab45279a92ac9_720w.jpg


八、循环引用问题


1、PHP7.1效果

<?php

$a = array('life');
xdebug_debug_zval( 'a' );
echo PHP_EOL;
$a[] = &$a;
xdebug_debug_zval('a');
 

v2-b8561e2a2611c586184a54592a5a053f_720w.jpg


可以看到,箭头方向表示的就是递归循环引用了

v2-7370102e36504e5f90a1a64020a23781_720w.jpg


说明:

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


结果:

尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于子元素“1”仍然指向数组本身,所以这个容器不能被清除。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。


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


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

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

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


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


九、垃圾回收算法


1、对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval,并将每个zval的refcount减1,同时为了避免对同一zval多次减1(因为可能不同的根能遍历到同一个zval),每次对某个zval减1后就对其标记为“已减”。

2、再次对每个缓冲区中的根zval深度优先遍历,如果某个zval的refcount不为0,则对其加1,否则保持其为0。

3、清空根缓冲区中的所有根(注意是把这些zval从缓冲区中清除而不是销毁它们),然后销毁所有refcount为0的zval,并收回其内存。


如果不能完全理解也没有关系,只需记住PHP5.3的垃圾回收算法有以下几点特性:

1、并不是每次refcount减少时都进入回收周期,只有根缓冲区满额后在开始垃圾回收。

2、可以解决循环引用问题。

3、可以总将内存泄露保持在一个阈值以下。


1

  • 发表于 2020-04-21 14:05
  • 阅读 ( 435 )
  • 分类:PHP开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1470 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章