美文网首页
PHP gc机制

PHP gc机制

作者: 譬如朝潞 | 来源:发表于2021-11-04 11:37 被阅读0次

    在理解PHP垃圾回收机制(GC)之前,先了解一下变量的存储。
    php中变量存在于一个zval的变量容器中。结构如下:


    php变量容器示意图

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

    • is_ref:是个bool值,用来区分变量是否属于引用集合。什么意思呢,你可以这么认为:表示变量是否有一个以上的别名。
    • refcount:计数器,表示指向这个zval变量容器的变量个数。
      两者之间有这么一个默认关系:当refcount值为1时,is_ref的值为false。因为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存储。对象类似。下面给出了数组的存储形象表示

    php复合类型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容器 是一样的。如下:

    image.png

    这样就形成了环状引用。

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

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


    image.png

    尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“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机制

          本文链接:https://www.haomeiwen.com/subject/ntexzltx.html