简单谈谈PHP的GC 您所在的位置:网站首页 php的垃圾收集机制 简单谈谈PHP的GC

简单谈谈PHP的GC

2024-01-02 01:48| 来源: 网络整理| 查看: 265

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)中,在根缓冲区满了的时候,也会对垃圾缓冲区进行一次回收。

具体见文档https://www.php.net/manual/zh/features.gc.collecting-cycles.php



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有