浅析PHP的垃圾回收

  • 标签: PHP基础
  • 发表时间:2020年05月27日
  • 作者:hhao
  • 浏览次数:94

垃圾回收机制


PHP 垃圾回收机制(Garbage Collector 简称GC)

垃圾回收机制是一种自动的内存管理机制,当一个变量在程序中不再被需要时,应该予以释放,避免不必要的内存浪费,这种内存资源管理称为垃圾回收。

PHP变量内部存储结构


每个PHP变量都是存储在一个叫“zval”的变量容器。当在PHP中的定义一个变量时,内核会自动把变量的信息存储到一个HashTable实现的符号表中并把它指向一个容器(结构体-zval)。

//zval容器
struct _zval_struct {
	union {
		long lval;
		double dval;
		struct {
			char *val;
			int len;
		} str;
		HashTable *ht;
		zend_object_value obj;
	} value;     //变量value值,是一个联合体
	zend_uint refcount__gc;   //引用计数内存中使用次数,为0删除该变量
	zend_uchar type;	  //变量类型
	zend_uchar is_ref__gc;    //区分是否是引用变量
};

==上面zval结构体是php5.3版本之后php7之前的结构,php5.3之前因为没有引入新的垃圾回收机制,即GC,所以命名也没有_gc;而php7版本之后由于性能问题所以改写了zval结构== 从上面的结构体内容可以看出php变量都会由变量类型、value值、引用计数次数和是否是引用变量四部分组成。

引用计数基础


php早期垃圾回收方式是使用zval容器中的引用计数次数,当变量容器的ref_count计数清0时,表示该变量容器就会被销毁,实现内存回收。

通过对数据存储的物理空间多附加一个计数器空间,当其他数据与其相关时,计数器加一,反之,相关解除时计数器减一。定期遍历检查各个存储对象的的计数器,计数器为零的话,则认为该对象已经被抛弃而应将其所占的内存空间回收。 非array和object变量

举例:

<?php
//生成一个新的zval容器
 $a = "new string";
//如果你已经安装了» Xdebug,通过调用函数 xdebug_debug_zval()显示"refcount"和"is_ref"的值
 xdebug_debug_zval('a'); 
?>

结果:

a: (refcount=1, is_ref=0)='new string'

array和object变量 array和object这样的复合类型时,array和 object类型的变量把它们的成员或属性存在自己的符号表中。将生成三个zval变量容器

举例:

$b = [
'name' => '我的博客',
'number' => 3
];
xdebug_debug_zval('b')

结果:

b: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='我的博客', 'number' => (refcount=1, is_ref=0)=3)

赋值原理(写时复制技术)

如果通过赋值的方式赋值给变量时不会申请新内存来存放新变量所保存的值,而是简单的通过一个计数器来共用内存,只有在其中的一个引用指向变量的值发生变化时,才申请新空间来保存值内容以减少对内存的占用。

举例:

$a = [
'name' => '我的博客',
'number' => 3
]; //创建一个变量容器,变量a指向给变量容器,a的ref_count为1
$b = $a; //变量b也指向变量a指向的变量容器,a和b的ref_count为2
xdebug_debug_zval('a', 'b');
$b['name'] = '我的博客1';//变量b的其中一个元素发生改变,此时会复制出一个新的变量容器,变量b重新指向新的变量容器,a和b的ref_count变成1
xdebug_debug_zval('a', 'b'); 

结果:

a: (refcount=2, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='我的博客', 'number' => (refcount=1, is_ref=0)=3)
b: (refcount=2, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='我的博客', 'number' => (refcount=1, is_ref=0)=3)
a: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='我的博客', 'number' => (refcount=1, is_ref=0)=3)
b: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='我的博客1', 'number' => (refcount=1, is_ref=0)=3)

循环引用引发的内存泄露

php5.3版本之前的垃圾回收机制存在一个漏洞,即当数组或对象内部子元素引用其父元素,而此时如果发生了删除其父元素的情况,此变量容器并不会被删除,因为其子元素还在指向该变量容器,但是由于所有作用域内都没有指向该变量容器的符号,所以无法被清除,因此会发生内存泄漏,直到该脚本执行结束

举例:

$a = array( 'one' );
$a[] = &$a;
xdebug_debug_zval( 'a' );

如图: 12f37b1c6963c1c5c18f30495416a197looparray.png

举例:

unset($a);
xdebug_debug_zval('a');

如图: 12f37b1c6963c1c5c18f30495416a197leakarray.png

引用计数系统的同步周期回收


由于引用计数算法存在无法回收循环应用导致的内存泄露问题,在PHP5.3版本之后通过采用 【引用计数系统的同步周期回收】 算法实现内存管理。引用计数系统的同步周期回收算法是一个改良版本的引用计数算法,它在引用基础上做出了如下几个方面的增强:

  • 引入了可能根(possible root)的概念:通过引用计数相关学习,我们知道如果一个变量(zval)被引用,要么是被全局符号表中的符号引用(即变量),要么被复杂类型(如数组)的 zval 中的符号(数组的元素)引用,那么这个 zval 变量容器就是「可能根」。
  • 引入根缓冲区(root buffer)的概念:根缓冲区用于存放所有「可能根」,它是固定大小的,默认可存 10000 个可能根,当PHP发现由循环引用的zval是就会加入到缓冲区中,当缓冲区达到指定的数量时,就会进行垃圾回收解决循环引用导致的内存泄漏问题。
  • 回收周期:当缓冲区满时,对缓冲区中的所有可能根进行垃圾回收处理。

优化后的引用计数算法

  • 将内存泄露控制在阀值内,这个由缓存区实现,达到缓冲区大小执行新一轮垃圾回收;
  • 提升了垃圾回收性能,不是每次 refcount 减 1 都执行回收处理,而是等到根缓冲区满时才开始执行垃圾回收。

参考PHP回收周期(Collecting Cycles)

上一篇:JWT简介
评论