简单谈谈PHP的GC-垃圾回收机制

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);

a 1 和 a1和 a1a2的属性互相引用时,unset( a 1 , a1, 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CRMEB定制开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值