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被当成垃圾回收,这就出现了可利用的点。