PHP垃圾回收机制

GC回收机制

PHP与其它语言一样,会有垃圾回收机制,就是我们所说的GC机制,能够销毁内存空间,防止内存溢出,PHP的垃圾回收机制利用引用计数的机制来确定是否需要回收,简单来说,就是引用计数为0的变量可以进行回收,而这些变量存在于一个"zval"的变量容器中,容器中包含了变量的类型和值,以及两个字节的信息,一个是标识变量是否是引用集合,用于分开普通变量和引用变量,另一个是"refcount",用于确定指向此变量的个数,即引用的个数。GC回收机制可以通过修改PHP配置开实现开启和关闭。

//php.ini
zend.enable_gc = On(Off)

引用技术的知识

普通类型的引用计数

在PHP5中,当一个变量=赋值给另一个变量,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount中加1,当原变量发生改变,才会分配内存空间,而PHP7不一样,PHP7不会再给变量如整型,字符串等进行计数,如果给一个变量&赋值,之前 = 赋值的变量会分配空间。


$a = 1;
xdebug_debug_zval('a');
//output -> a: (refcount=0, is_ref=0)=1
 
$b = $a;
xdebug_debug_zval('a');
//output -> a: (refcount=0, is_ref=0)=1
 
$c = &$a;
xdebug_debug_zval('a');
//output -> a: (refcount=2, is_ref=1)=1

//tips: refcount不对数字,字符串,浮点数等标量进行refcount进行计数,当使用&引用后,is_ref区分引用变量,refcount变为了2。

复合类型的计数知识

和标量不一样,考虑复合类型如array,object时,会生成多个eval变量容器。

$a = array( 'name' => 'aiwin', 'number' => 42 );
xdebug_debug_zval( 'a' );
//output -> a:
//(refcount=2, is_ref=0)
//array (size=2)
//	'name' => (refcount=1, is_ref=0)string 'aiwin' (length=5)
//	'number' => (refcount=0, is_ref=0)int 42
 
class Test{
    public $a = 1;
    public $b = 2;
 
    function handle(){
        echo 'hehe';
    }
}
 
$test = new Test();
xdebug_debug_zval('test');
//output -> test:
//(refcount=1, is_ref=0)
//object(Test)[1]
//	public 'a' => (refcount=0, is_ref=0) int 1
//	public 'b' => (refcount=0, is_ref=0) int 2

可以看到, 分配了多个zval容器,其中数组a和类Test中的变量指向数字标量依旧不进行refcount的计数。

$a = array('name'=>"aiwin","float"=>4.25);
xdebug_debug_zval( 'a' );
//output -> a:
//(refcount=2, is_ref=0)
//array (size=2)
//	'name' => (refcount=1, is_ref=0)string 'aiwin' (length=5)
//	'float' => (refcount=0, is_ref=0)float 4.25
 
$a[] = &$a;
xdebug_debug_zval('a');
//output -> a:
//array (size=3)
//	'name' => (refcount=2, is_ref=0)string 'aiwin' (length=5)
//	'float' => (refcount=0, is_ref=0)float 4.25
//	0 => (refcount=2, is_ref=1)
//	&array

第二个is_ref=1,用于区别引用变量和普通变量,由于数组a被&引用了1次,因此refcount为2,浮点型refcount依旧不计数,数组中的name第一次先计数为1,再被引用了后,计数升为2,最后的&array表示递归循环引用。

GC 垃圾回收机制的使用

那么,说到底GC回收机制有什么能利用的点呢,这就需要联系起PHP的魔术方法__destrcut函数,在程序结束后php会自动调用__destruct进行自动销毁,如果程序报错或者抛出异常则不会触发__destruct。先来简单看看实际情况下GC回收机制的工作。


class aiwin{
    public $test;
    public function __construct($test)
    {
        $this->test = $test;
        echo $this->test."构造函数执行"."
"; } public function __destruct(){ echo $this->test."销毁函数执行"."
"; } } new aiwin(1); $a = new aiwin(2); $b = new aiwin(3); //output //1构造函数执行 //1销毁函数执行 //2构造函数执行 //3构造函数执行 //2销毁函数执行 //3销毁函数执行

可以看到直接使用new创建类的时,没有进行指向,直接就触发了__destrcut函数当作垃圾回收了。

那么假如使用数组的形式来存储类的创建呢

class aiwin{
    public $test;
    public function __construct($test)
    {
        $this->test = $test;
        echo $this->test."构造函数执行"."
"; } public function __destruct(){ echo $this->test."销毁函数执行"."
"; } } $a=array(new aiwin(1),0); $a[0]=$a[1]; $b=new aiwin(2); $c=new aiwin(3); //output //1构造函数执行 //1销毁函数执行 //2构造函数执行 //3构造函数执行 //3销毁函数执行 //2销毁函数执行

new出来的类a依旧被立即销毁,因为最后把数组的第二项即0赋值给了数组的第一项,也就相等于new aiwin(1)执行是NULL,也会触发__destruct被当成垃圾回收,这就出现了可利用的点。

tag(s): none
show comments · back · home
Edit with Markdown
召唤看板娘