前言
GC的全称是Garbage Collection也就是垃圾回收的意思,在PHP中,是使用引用计数和回收周期来自动管理内存对象的,当一个对象被设置为NULL,或者没有任何指针指向时,他就会变成垃圾,被GC机制回收掉。
环境配置
php.ini终配置好xdebug,xdebug_debug_zval
是用来查看容器变量内容的函数
<?php $a = "F12"; xdebug_debug_zval("a"); ?>
在PHP GC机制中,当程序终止时就会让变量的refcount
减1,如果refcount-1
为0的话,就会销毁回收该变量
引用计数
is_ref
表示该变量是否被引用,操作系统学的好的同学应该很容易理解该内容
<?php $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类型时,也是一样的规则
<?php $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销毁会发生什么?
<?php $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)
<?php $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
<?php 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会怎样?
<?php 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:
<?php 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!
,但是因为触发了异常,所以对象并没有被回收
我们修改一下代码:
<?php 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,并不赋值是一个道理
思路二
这种方法更加简单粗暴,我们只需要让序列化的数据出错,那么当反序列化时出错时,也会让该对象提前回收
<?php 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!