PHP目前的开发框架,除了主流常用的FPM框架,想必就是基于swoole拓展的常驻内存开发了。
我们在FPM的开发模式中,每一次脚本程序结束之后,所有变量都会被销毁,内存会被释放,所以我们不用太担心。
但是常驻内存开发模式就不一样了,如果不注意变量内存的使用,无法很好的管理内存问题,会造成内存泄露。
所以,我们一定要熟悉PHP的垃圾回收机制(Garbage Collector 简称GC)
在PHP7+版本中,有关于变量内存的操作特性,采用了写时复制,也就是说, 在必要的时候才会进行深拷贝(即发生写的时候才会进行深拷贝).
当变量值为interned string字符串型(变量名,函数名,静态字符串,类名等)时,变量值存储在静态区,内存回收被系统全局接管,引用计数将一直为1 。
当赋值变量的值为 整型,浮点型 时,php7底层将会直接把值存储(php7的结构体将会直接存储简单数据类型),refcount将为0,我们用代码来看一下:
$a = 'chris'.time();
$b = $a; //此时$b指向$a的同一个内存地址
$c = $a; //一样
xdebug_debug_zval('a');
//a:(refcount=3, is_ref=0)string 'chris1614780053' (length=15)
我们通过Xdebug来观察变量的信息,他们指的都是同一个内存地址,是引用。
$a = 'chris'.time();
$b = '青玄';
$c = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
//a:(refcount=2, is_ref=0)string 'chris1614780283' (length=15)
//b:(interned, is_ref=0)string '青玄' (length=6) //存在了静态区
我们通过Xdebug来观察变量的信息,这时$b的值已经发生了变化,这时候,才会使用新的内存空间,$a的引用次数-1。
每个php变量存在一个叫”zval”的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是”is_ref”,是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是”refcount”,用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。 官方文档的描述
简单来说,就是给变量引用的次数进行计算,当计数refcount不等于0时,说明这个变量已经被引用,不能直接被回收,否则可以直接回收,用代码来看看把
$a='chris'.time();
$b=$a;
$c=$a;
$b='青玄';
unset($c);
xdebug_debug_zval('a');
//a:(refcount=1, is_ref=0)string 'chris1614780526'(length=5)
//我们可以看到,删除$c,并不能把$a删除,因为refcount=1
function a(){
class A {
public $ref;
public $name;
public function __construct($name) {
$this->name = $name;
echo($this->name.'->__construct();'.PHP_EOL);
}
public function __destruct() {
echo($this->name.'->__destruct();'.PHP_EOL);
}
}
$a1 = new A('$a1');
$a2 = new A('$a2');
$a3 = new A('$3');
$a1->ref = $a2;
$a2->ref = $a1;
unset($a1);
unset($a2);
echo('exit(1);'.PHP_EOL);
}
a();
echo('exit(2);'.PHP_EOL);
当$a1和$a2的属性互相引用时,unset($a1,$a2) 只能删除变量的引用,却没有真正的删除类的变量,这是为什么呢?
首先,类的实例化变量分为2个步骤
1:开辟类存储空间,用于存储类数据
2:实例化一个变量,类型为class,值指向类存储空间
当给变量赋值成功后,类的引用计数为1,同时,a1->ref指向了a2,导致a2类引用计数增加1,同时a1类被a2->ref引用,a1引用计数增加1
当unset时,只会删除类的变量引用,也就是-1,但是该类其实还存在了一次引用(类的互相引用),
这将造成这2个类内存永远无法释放,直到被gc机制循环查找回收,或脚本终止回收(域结束无法回收).
每个方法/函数都作为一个作用域,当运行完该作用域时,将会回收作用域内的所有变量,全局变量只有在脚本结束后才会回收。
我们可以通过以下方式来手动回收:
另外:为避免不得不检查所有引用计数可能减少的垃圾周期,PHP会有算法把疑似垃圾的变量,放在根缓冲区(root buffer)中,在根缓冲区满了的时候,也会对垃圾缓冲区进行一次回收。
具体见文档https://www.php.net/manual/zh/features.gc.collecting-cycles.php
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。