美文网首页
PHPER必了解——PHP GC机制

PHPER必了解——PHP GC机制

作者: PHP的点滴 | 来源:发表于2020-05-27 11:24 被阅读0次

    前言

    • PHP5.3之前(不包括5.3)的垃圾回收机制,是没有专门的垃圾回收器。
      只是简单的判断了一下变量的zval的refcount是否为0,是的话就释放否则不释放直至进程结束。
    • PHP5.3出现了专门负责清理垃圾数据、防止内存泄漏的GC。

    PHP变量 zval 容器

    每个php变量存在一个叫"zval"的变量【zval容器】中
    name:字段值
    is_ref:标识这个变量是否是属于引用集合变量
    refcount:表示指向这个zval变量容器的变量(也称符号即symbol)个数
    当被变量引用时refcount+1,当变量撤掉时refcount-1,当计数器=0时,表明内存对象没有被使用,该内存对象则进行销毁,垃圾回收完成。

    变量引用示例

    1. refcount计数减1,unset并非一定会释放内存,当有两个变量指向的时候,并非会释放变量占用的内存,只是refcount减1
      【变量销毁】
    $name = '张三';
    xdebug_debug_zval('name');
    $test = &$name;
    xdebug_debug_zval('name');
    unset($test);
    xdebug_debug_zval('name');
    
    //结果如下:
    name:
    (refcount=1, is_ref=0)string '张三' (length=6)
    name:
    (refcount=2, is_ref=0)string '张三' (length=6)
    name:
    (refcount=1, is_ref=0)string '张三' (length=6)
    
    

    【环行引用】php变量的回收机制只是简单的通过计数来处理(当refcount=0时,会回收内存),但这样会出现一个问题(老版本会导致内存泄漏)

    $a = ['one']; //--- step0
    xdebug_debug_zval('a');
    
    //结果
     a:
    (refcount=2, is_ref=0)
    array (size=1)
      0 => (refcount=1, is_ref=0)string 'one' (length=3)
    
    $a[] = &$a; // --- step1
    xdebug_debug_zval('a');
    
    //结果
    a:
    (refcount=2, is_ref=1)
    array (size=2)
      0 => (refcount=1, is_ref=0)string 'one' (length=3)
      1 => (refcount=2, is_ref=1)
        &array<
    
    
    unset($a);  //--- step2
    xdebug_debug_zval('a');
    
    //结果
    a: no such symbol
    
    
    • step1执行unset之前,$a的refcount 为2,
    • 执行unset之后,$a的refcout为1
    • 因为是1不等于0,不能被回收内存,即为垃圾
    • 当然,在php脚本执行完毕后,所分配的内存将全部被回收(php-fpm模式)
    • 但是现在php除了应用于脚本以外,更多的地方用于写守护服务(swoole),可能很久(比如一年)才结束脚本,这期间例如上面的程序会产生内存溢出

    注意:unset并不能释放内存,需要看zval的refcount是否为0(敲黑板)

    潜在的内存变化

    print_r(memory_get_usage());
    for($i=0;$i<100;$i++)
    {
        $a = "test".$i;
        $$a = "temp";
    }
    print_r(memory_get_usage());
    for($i=0;$i<100;$i++)
    {
        $a = "test".$i;
        unset($$a);
    }
    print_r(memory_get_usage());
    

    结果:
    2006640
    2017392
    2014224

    内存没有全部回收回来!
    对于php的核心结构Hashtable来说,由于未知性,定义的时候不可能一次性分配足够多的内存块。
    所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少,所以就出现了上述的情况:
    1.当存入100个变量的时候,符号表不够用了就进行一次扩容
    2.当unset的时候只释放了”为变量值分配内存”,而“为变量名分配内存”是在符号表的,符号表并没有缩小
    3.所以没收回来的内存是被符号表占去了。

    1.php的内存申请: php并非简单的向os申请内存,而是会申请一大块内存,把其中一部分分给申请者,这样当再有逻辑来申请内存的时候,就不需要向os申请了,避免了频繁调用。当内存不够的时候才会再次申请
    2.php的内存释放:当释放内存的时候,php并非会把内存还给os,而是把内存轨道自己维护的空闲内存列表,以便重复利用

    GC垃圾回收机制

    基本准则:

    • 1.如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
    • 2.如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
    • 3.如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾

    php官方手册的配图


    图解

    A:为了避免每次变量的refcount减少的时候都调用GC的算法进行垃圾判断,此算法会先把所有前面准则3情况下的zval节点放入一个节点(root)缓冲区【root buffer】,并且将这些zval节点标记成紫色,同时算法必须确保每一个zval节点在缓冲区中之出现一次。当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。

    B:当缓冲区满了之后,算法以深度优先对每一个节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。

    C:算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)

    D:遍历zval节点,将C中标记成白色的节点zval释放掉。

    相关函数

    缓存区达到临界值时,遍历删除是垃圾的值

    • __destruct() 析构函数,是在垃圾对象被回收时执行。

    • unset 销毁的是指向对象的变量,而不是这个对象。

    • $a=null 将变量指向空容器

    相关文章

      网友评论

          本文标题:PHPER必了解——PHP GC机制

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