当前位置: 首页 > 后端技术 > PHP

PHP的垃圾回收机制-引用计数基础知识(5.3)

时间:2023-03-30 03:52:09 PHP

PHP的垃圾回收机制Garbagecollector,全称GarbageCollection,简称GC,在5.3版本之前,它只是简单的判断是否引用了zval的refcount变量为0,如果是,则释放,直到进程结束(变量内存溢出的隐患)。如果您已经安装了?Xdebug,您可以通过调用函数xdebug_debug_zval()来显示“refcount”和“is_ref”的值。引用计数基础知识1、一个zval变量容器:内容类型值is_ref是一个bool值,用来标识这个变量是否属于一个引用集(referenceset)。zval变量容器内部还有一个引用计数机制来优化内存使用refcount指向这个zval变量容器的变量(也叫symbols或symbols)的个数所有的symbol都存储在一个symboltable中,每个symbol都有一个scope(作用域),而那些主要的脚本(如:浏览器请求的脚本))以及每个函数或方法也有一个作用域。当一个变量被赋常量值时,会生成一个zval变量容器1.生成一个新的zval容器参考上面的定义,分别是:生成的变量容器,其中:typestringvaluenewstringis_reffalseRefcount12.增加一个zval的引用计数将一个变量赋值给另一个变量会增加引用次数(refcount)。Typestringvaluenewstringis_reftrueRefcount2引用次数为2,因为同一个变量容器关联了变量a和变量b。PHP在不需要的时候不会复制生成的变量容器。当“refcount”变为0时,变量容器被销毁。当与变量容器关联的任何变量离开其作用域时(例如:函数执行结束),或者在变量上调用函数unset()时,“refcount”它会减少1.3,减少引用计数a:(refcount=3,is_ref=0)='newstring'a:(refcount=1,is_ref=0)='newstring'如果我们执行unset($a);现在,包含类型和值的变量容器已从内存中删除。4.复合类型当考虑像数组和对象这样的复合类型时,事情会稍微复杂一些。与标量类型的值不同,数组和对象类型的变量将其成员或属性存储在自己的符号表中。这意味着下面的示例将生成三个zval变量容器。'life','number'=>42);xdebug_debug_zval('a');?>输出a:(refcount=1,is_ref=0)=array('meaning'=>(refcount=1,is_ref=0)='life','number'=>(refcount=1,is_ref=0)=42)三个zval变量容器分别是:a、meaning和number。增加和减少“refcount”的规则同上。接下来,我们向数组添加另一个元素并将其值设置为数组中现有元素的值:Addanexistingelementtothearray'life','number'=>42);$a['life']=$a['meaning'];xdebug_debug_zval('a');?>输出a:(refcount=1,is_ref=0)=array('meaning'=>(refcount=2,is_ref=0)='life','number'=>(refcount=1,is_ref=0)=42,'life'=>(refcount=2,is_ref=0)='life')有图有真相从上面的xdebug输出信息我们可以看出,原来的数组元素和新增的数组元素关联的是同一个“refcount”2的zval变量容器。尽管Xdebug输出显示两个值为“life”的zval变量容器,但它们实际上是同一个容器。函数xdebug_debug_zval()不显示这些信息,但是通过显示内存指针信息可以看到。删除数组中的元素类似于从范围中删除变量。删除后,数组中元素所在容器的“refcount”值减少。同样,当“refcount”为0时,变量容器从内存中删除,下面的另一个例子可以说明:'life','number'=>42);$a['life']=$a['meaning'];unset($a['meaning'],$a['number']);xdebug_debug_zval('a');?>Outputa:(refcount=1,is_ref=0)=array('life'=>(refcount=1,is_ref=0)='life')现在,当我们添加一个数组本身作为这个数组的元素时,事情变得有趣了,下一个例子将说明这。在示例中我们添加了引用运算符,否则php会生成一个副本。将数组作为元素添加到自己输出a:(refcount=2,is_ref=1)=array(0=>(refcount=1,is_ref=0)='one',1=>(refcount=2,is_ref=1)=...)看图可以看到数组变量(a)同时这个数组的第二个元素(1)指向的变量容器中的“refcount”也是2,上面输出中的“...”表示发生了递归,这显然意味着在在这种情况下,“...”指向原始数组。和之前一样,对变量调用unset会删除符号,它指向的变量容器中的引用计数也会减1。所以,如果我们在执行完上面的代码后对变量$a调用unset,那么数组元素“1”指向的变量$a和变量container的引用计数会减1,从“2”减为“1”。下面的例子可以说明:(refcount=1,is_ref=0)='one',1=>(refcount=1,is_ref=1)=...)看看图说2.变量容器清理问题(CleanupProblems)?虽然不再有一些Anysymbolsinthisscope指向这个结构体(即变量容器),并且由于数组元素“1”仍然指向数组本身,这个容器不能被清除。由于没有其他符号指向它,用户没有办法清理这个结构,导致内存泄漏。幸运的是,php会在脚本执行结束时清除这个数据结构,但是在php清除之前,会消耗大量内存。如果您正在实施分析算法,或者做其他事情,比如孩子指向其父母,这种情况会经常发生。当然,对象也会发生同样的情况,而且实际上更可能发生在对象身上,因为对象总是被隐式引用。上面的情况如果只出现一两次还好,但是如果有几千甚至几十万次内存泄漏,这显然是个大问题。此类问题往往发生在长时间运行的脚本中,例如请求很少结束的守护进程,或者单元测试中的大集合。后者的一个例子:在对庞大的eZ(著名的PHPLibrary)组件库的模板组件进行单元测试时,可能会出现问题。有时测试可能需要消耗2GB内存,而测试服务器很可能没有这么大的内存。PHP手册