PHP的垃圾回收机制相关知识点整理
本篇内容主要讲解"PHP的垃圾回收机制相关知识点整理",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"PHP的垃圾回收机制相关知识点整理"吧!
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作用域的生命周期和变量回收
每个方法/函数都作为一个作用域,当运行完该作用域时,将会回收作用域内的所有变量,全局变量只有在脚本结束后才会回收。
我们可以通过以下方式来手动回收:
unset() : unset的回收原理其实就是引用计数-1,当引用计数-1之后为0时,将会直接回收该变量,否则不做操作(这就是上面内存泄漏的原因,引用计数-1并没有等于0)。
赋值为null :=null和unset($a),作用其实都为一致,null将变量值赋值为null,原先的变量值引用计数-1,而unset是将变量名从php底层变量表中清理,并将变量值引用计数-1,唯一的区别在于,=null,变量名还存在,而unset之后,该变量就没了。
变量覆盖回收:通过给变量赋值其他值(例如null)进行回收,但是从程序的内存占用来说,覆盖变量并不是意义上的内存回收,只是将变量的内存修改为了其他值.内存不会直接清空。
gc_collect_cycles :强制执行周期回收,在PHP执行中,一旦根缓冲区满了或者调用gc_collect_cycles() 函数时,就会执行垃圾回收
另外:为避免不得不检查所有引用计数可能减少的垃圾周期,PHP会有算法把疑似垃圾的变量,放在根缓冲区(root buffer)中,在根缓冲区满了的时候,也会对垃圾缓冲区进行一次回收。
到此,相信大家对"PHP的垃圾回收机制相关知识点整理"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!