• PHP GC回收机制详解


    前言

    GC的全称是Garbage Collection也就是垃圾回收的意思,在PHP中,是使用引用计数和回收周期来自动管理内存对象的,当一个对象被设置为NULL,或者没有任何指针指向时,他就会变成垃圾,被GC机制回收掉。

    环境配置

    php.ini终配置好xdebug,xdebug_debug_zval是用来查看容器变量内容的函数

     
    $a = "F12";
    xdebug_debug_zval("a");
    ?>
    


    在PHP GC机制中,当程序终止时就会让变量的refcount减1,如果refcount-1为0的话,就会销毁回收该变量

    引用计数

    is_ref表示该变量是否被引用,操作系统学的好的同学应该很容易理解该内容

     
      $a = "F12";
      $b = &$a;
      xdebug_debug_zval("a");
    ?>
    
    # 运行结果
    a: (refcount=2, is_ref=1)='F12'
    

    $b是$a的引用,所以is_ref=1,同时refcount也会加1,因为此时是有两个变量的(两变量指向同一个地址),所以销毁时要让refcount减2。
    当变量是array类型时,也是一样的规则

     
      $a = "F12";
      $arr = array(0=>"test", 1=>&$a);
      xdebug_debug_zval("arr");
    ?>
    # 运行结果
    arr: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='test', 1 => (refcount=2, is_ref=1)='F12')
    

    如果我们在引用前将$a销毁会发生什么?

     
      $a = "F12";
      unset($a);
      $arr = array(0=>"test", 1=>&$a);
      xdebug_debug_zval("a");
      xdebug_debug_zval("arr");
    ?>
    # 运行结果
    a: (refcount=2, is_ref=1)=NULL
    arr: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='test', 1 => (refcount=2, is_ref=1)=NULL)
    
     
      $a = "F12";
      $arr = array(0=>"test", 1=>&$a);
      unset($a);
      xdebug_debug_zval("a");
      xdebug_debug_zval("arr");
    ?>
    # 运行结果
    a: no such symbol
    arr: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='test', 1 => (refcount=1, is_ref=1)='F12')
    

    第一种情况,$a没有被销毁,因为在之后又引用了$a,所以$a只是指向了一个NULL,第二种情况就把$a销毁了

    PHP GC在反序列化中的使用

    一个简单的demo

    
    class gc{
        public $num;
        public function __construct($num)
        {
            $this->num=$num;
            echo "construct(".$num.")"."\n";
        }
        public function __destruct()
        {
            echo "destruct(".$this->num.")"."\n";
        }
    }
    $a=new gc(1);
    $b=new gc(2);
    $c=new gc(3);
    
    # 运行结果
    construct(1)
    construct(2)
    construct(3)
    destruct(3)
    destruct(2)
    destruct(1)
    

    先创建的对象最后销毁,看看变量的内容情况:

    可以看到refcount为1,所以当程序结束时,减1就会被回收
    如果我们不把new的gc对象赋值给$a会怎样?

    
    class gc{
        public $num;
        public function __construct($num)
        {
            $this->num=$num;
            echo "construct(".$num.")"."\n";
        }
        public function __destruct()
        {
            echo "destruct(".$this->num.")"."\n";
        }
    }
    new gc(1);
    $b=new gc(2);
    $c=new gc(3);
    
    # 运行结果
    construct(1)
    destruct(1)
    construct(2)
    construct(3)
    destruct(3)
    destruct(2)
    

    可以看到第一个gc对象,创建完就被回收了,因为没被其它变量引用,它的refcount一开始就是0,所以直接被回收

    绕过Exception异常

    思路一

    一个简单的demo:

    
    class gc{
        public $num;
        public function __construct($num)
        {
            $this->num=$num;
        }
        public function __destruct()
        {
            echo "Hello World!";
        }
    }
    $a = new gc(1);
    $ser = serialize($a);
    $b = unserialize($ser);
    throw new Exception("F12 is bad");
    

    正常来说会输出一个Hello World!,但是因为触发了异常,所以对象并没有被回收

    我们修改一下代码:

    
    class gc{
        public $num;
        public function __construct($num)
        {
            $this->num=$num;
        }
        public function __destruct()
        {
            echo "Hello World!";
        }
    }
    $a = array(0=>new gc(1),1=>1);
    $ser = serialize($a);
    echo $ser;
    $ser = 'a:2:{i:0;O:2:"gc":1:{s:3:"num";i:1;}i:0;i:1;}';
    $b = unserialize($ser);
    throw new Exception("F12 is bad");
    

    这里我们我们修改序列化的内容,将$a[0]随便指向谁,从而使new的gc对象没有引用的变量,所以触发提前回收,跟上面举的直接new gc,并不赋值是一个道理

    思路二

    这种方法更加简单粗暴,我们只需要让序列化的数据出错,那么当反序列化时出错时,也会让该对象提前回收

    
    class gc{
        public $num;
        public function __construct($num)
        {
            $this->num=$num;
        }
        public function __destruct()
        {
            echo "Hello World!";
        }
    }
    $a = new gc(1);
    $ser = serialize($a);
    echo $ser;
    $ser = 'O:2:"gc":1:{s:3:"num";i:1;';
    $b = unserialize($ser);
    throw new Exception("F12 is bad");
    

    这里我们删去一个},依然输出了Hello World!


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/F12-blog/p/17987039
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    如何在麒麟上安装 ONLYOFFICE 桌面编辑器
    idea远程debug调试
    继续预训练对大语言模型的影响
    spark内存管理
    构建 App 的方法
    【大数据学习篇6】 Spark操作统计分析数据操作
    [附源码]java毕业设计疫情居家隔离服务系统
    2023年中国半导体IP行业发展概况及趋势分析:半导体IP的市场空间广阔[图]
    数据集笔记:杭州地铁刷卡数据
    冲刺学习-MySQL-基础
  • 原文地址:https://www.cnblogs.com/F12-blog/p/17987039