php内存回收机制的学习

  今天朋友去面试,回来问了一下怎么样,结果他说一脸懵逼,看来我们平时还是学习的太少了啊。于是比较好奇,果断问了一下都有哪些问题,朋友说第一个问题就是“描述PHP的垃圾回收机制”,我当时听了也是一脸茫然,因为平时我们业务逻辑写的太多,很少去关注这些,但是没办法,既然有人问这个问题,看来还是很有必要了解一下的。于是马上搜了一下,网上资料文章很多,看了几篇后加上自己的一些理解记录一下。

  首先看了一下官方手册,只有php5.3版本以后的才有了所谓的新的垃圾回收机制GC,那么以前是怎么干的呢?以前是基于引用计数的方式,这里就需要提一下引用计数的知识,官方手册里面说php的每个变量都是存在一个叫做zval的容器里面,这个容器不仅包含了这个变量的值和类型,还包含了另外两个重要的信息,“is_ref”和“refcount”,“is_ref”看名字就应该知道大概和引用相关,它是一个bool值,如果这个值是true那么代表这是一个引用变量,否则是普通变量。“refcount”指的是有多少个变量(符号)指向这个zval容器。

  比如一个变量$a="test",如果我们php安装了xdebug插件并且开启了插件,就可以用xdebug_debug_zval(“a”)来显示zval里面的值。这里会输出a:(refcount=1,is_ref=0)=“test”,可以看到refcount=1,因为这里有一个变量(符号)$a指向了这个zval容器,is_ref=0说明这个存放的是一个普通变量。

  如果我们进行一个操作$b=$a呢?按照常规的思路,应该是把$a的值复制一份给$b,然后$b也存放在另一个zval容器中,这个zval容器内容和$a那个一样。真的是这样吗?我们用xdebug_debug_zval(“a”)先输出$a对应的zval容器值,结果会输出a:(refcount=2,is_ref=0)="test",这里refcount变成了2 ,说明除了$a还有一个变量(符号)指向这个zval容器,那就是$b了啊,这么一来$a和$b指向的是同一个zval容器,那不是修改$b也会影响到$a了?其实不会的,因为当$b或者$a的值改变的时候,这个zval容器的refcount会减一,然后会复制一份让改变值的那个变量(符号)指向新的zval容器,这个时候就是我们刚才常规思路想的一样了,有了两个zval容器都是(refcount=1,is_ref=0)只是两个容器的值和类型分别是$a和$b的值和类型。

  那如果是引用赋值$c=&$a呢?这时$a和$c同样也指向同一个zval,即a,c:(refcount=2,is_ref=1)="test",这时候不光refcount加一,is_ref也变成了1也就是true,说明这是引用变量,那么改变$a和$c任何一个都会影响另一个的值。我们如果使用unset($c)的话,$a指向的容器的refcount就会减一变成1。如果我们再unset($a)的话,指向的zval容器的refcount就是0了,这个时候说明已经没有变量(符号)指向这个容器了,那么php引擎就会从内存中销毁释放这个容器。

  那如果$a是一个数组呢,它指向的zval容器会是怎样的?比如$a=array("1","2"),xdebug_debug_zval(“a”)会输出如下的信息:

a: (refcount=1, is_ref=0)=array (
   0 => (refcount=1, is_ref=0)=‘1‘,
   1 => (refcount=1, is_ref=0)=‘2‘)

  可以看到除了$a本身指向一个zval容器存放外,它的每一个元素也都分别指向一个zval容器,如果我要这样往$a中添加元素会怎样?

$a = array( ‘meaning‘ => ‘life‘, ‘number‘ => 42 );
$a[‘life‘] = $a[‘meaning‘];   //这里直接拿官方示例

  这个时候xdebug_debug_zval(“a”)会输出: key为‘meaning‘和‘life‘的值指向同一个zval容器,refcount=2

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

  如果我们在添加元素的时候,添加的是对数组本身的引用,又会变成什么样?

<?php
$a = array( ‘one‘ );
$a[] =& $a;
xdebug_debug_zval( ‘a‘ );
?>

这时会输出:

a: (refcount=2, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)=‘one‘,
   1 => (refcount=2, is_ref=1)=...
)

  $a数组本身指向的容器refcount变成了2,因为$a和$a[1]指向了这个容器,然而$a[1]又是$a的元素,这个元素又引用了$a本身,这就形成了一个闭环。

  如果这个时候来一句unset($a)呢?$a指向的容器refcount减一就会变成1,这个时候对于我们程序员来说已经不存在有可操作的变量(符号)指向这个容器了,但是refcount=1那么php引擎就不会销毁这个容器。

  那不是这个容器在内存中不就成了垃圾了吗?这种情况如果没有垃圾回收机制GC,那么就只有等到当前请求结束,脚本结束自动清除了。但是有时候我们会用到一些递归或者死循环这类的来做一些特殊的业务逻辑,这时候内存如果有上面的情况出现,就会导致内存泄漏,消耗很大的内存空间。

  所以才有了5.3版本新的内存回收机制的出现。先说说机制的三个基本规则:

  • 如果一个zval容器的refcount增加,说明有新的变量(符号)指向这个容器,那么这个容器当然不会是垃圾,它将被继续使用。
  • 如果一个zval容器的refcount减少到0了,那么说明没有变量(符号)指向这个容器,它就会被php引擎销毁。
  • 如果一个zval容易的refcount减少了,但是不是0,那么这个容器就有可能是垃圾,就会被垃圾回收机制所管理。

  怎么管理这些容器并判断哪些是垃圾呢?当发现某个容器有可能是垃圾时,这个容器会被放进一个内存缓冲区,当缓冲区满了时,就会进行垃圾回收算法来找出垃圾并销毁。这里具体的算法可以看看官方文档,我就用一个网友的总结来描述:

  对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。

  这个道理其实很简单,假设数组a的refcount等于m, a中有n个元素又指向a,如果m等于n,那么算法的结果是m减n,m-n=0,那么a就是垃圾,如果m>n,那么算法的结果m-n>0,所以a就不是垃圾了。m=n代表什么?  代表a的refcount都来自数组a元素的指向,代表除了a中的元素没有任何变量(符号)指向根zval容器,代表用户代码空间中无法再访问到这个zval,代表a是泄漏的内存,因此GC将a这个垃圾回收了。

  最后在哪里可以设置这个回收机制呢?默认的,PHP的垃圾回收机制是打开的,然后在配置文件 php.ini 里允许你修改它:zend.enable_gc 。除了修改配置zend.enable_gc是否开启 ,也能通过分别调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。如果想在根缓冲区还没满时强制执行周期回收,可以调用gc_collect_cycles()函数,这个函数将返回使用这个算法回收的周期数。

  当垃圾回收机制打开时,每当根缓存区存满时,就会执行查找算法。根缓存区有固定的大小,可存10,000个可能根,当然可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个10,000值。当垃圾回收机制关闭时,循环查找算法永不执行,然而,可能根将一直存在根缓冲区中,不管在配置中垃圾回收机制是否激活。

  当垃圾回收机制关闭时,如果根缓冲区存满了可能根,更多的可能根显然不会被记录。那些没被记录的可能根,将不会被这个算法来分析处理。如果他们是循环引用周期的一部分,将永不能被清除进而导致内存泄漏。

  即使在垃圾回收机制不可用时,可能根也被记录的原因是,相对于每次找到可能根后检查垃圾回收机制是否打开而言,记录可能根的操作更快。不过垃圾回收和分析机制本身要耗不少时间。

  也就是说关闭了回收机制也会往缓冲区丢疑似垃圾的容器,当缓冲区满了的时候不会执行回收算法,后面更多的疑似垃圾容器不会继续放进去,就可能导致内存泄漏。当开启回收机制后,就会从缓冲区中之前放入的容器中开始垃圾回收机制。

时间: 2024-10-27 09:16:30

php内存回收机制的学习的相关文章

(转)PHP zval内存回收机制和refcount_gc和is_ref_gc

出处 : http://blog.sina.com.cn/s/blog_75a2f94f0101gygh.html 对于PHP这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错.另一方面,除了要安全的申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免.我们来看下面这段PHP代码: $a = "hello"; $b =

关于仿照java的内存回收机制实现C++的自动内存回收的一点想法

java的内存回收机制是很高效的,对软件产生的额外影响很小.而在C++中的大多数智能指针都是采用的引用计数的策略实现,当计数到0时,将所指向的指针删除.这种智能指针当应用到比较大的对象或者动态内存分配的次数非常少时.对软件的性能不会有多大的影响,反而提高了对内存的使用效率.可是一旦使用动态内存分配的次数非常巨大的时候.不仅对内存的使用效率下降,软件的运行效率也会下降很多.这主要是因为,动态分配造成的存储碎片化使可用内存减少,cache命中率也会下降.对软件性能可能会造成几百倍的损失. 目前的想法

Android 操作系统的内存回收机制(转载)

http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application FrameworkApplication

Android 操作系统的内存回收机制

转自:http://android.jobbole.com/25169/ 简介:Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理.本文在 Application Framework 与 Linux 内核两个层次上,以进程为粒度,对 Android 操作系统的进程资源回收机制进行了剖析.读者可以从本文获得对 Android 应用程序的生存周期的进一步理解

Android 操作系统的内存回收机制[转]

转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/ Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application Framework Application Framewo

【转】Android 内存回收机制(默认回收与kernel回收)

Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application Framework Application Framework 将整个操作系统分隔成两个部分.对应用开发者而言,所有 APP 都是运行在 Application Framework 之上,而并不需要关心系统底层的

memcache的内存回收机制

memcache不会释放内存,而是重新利用. 在缓存的清除方面,memcache是不释放已分配内存.当已分配的内存所在的记录失效后,这段以往的内存空间,memcache只会重复利用. memcached的内存回收机制不是说你设置的key到了生命周期就自动从内存中清除的,这个时候必须有一个新的对象入驻请求这个大小的chunk或者 这个过期的对象被get的时候才会清除. 那当所有给memcache的内存都被占用了,这个时候,memcache有两个设置,要么报错,要么,就是用 LRU方法,把last

js内存回收机制

Javascript语言有自己的一套内存回收机制,一般情况下局部变量和对象使用完就会被系统自动回收,无需我们理会.但是碰到闭包的情况这些变量和对象是不会被回收的,对于普通的web站点,页面刷新或跳转这些内存也会被回收.如果是单页web站点,页面切换及数据请求都是通过ajax无刷新机制实现的,页面资源无法自动回收,时间长了会严重影响性能,造成内存泄漏甚至页面崩溃直接退出,这时候手动释放不用资源就非常必要了,包含删除dom.释放对象等想手动释放含有闭包的对象时,必须先将引用对象属性的事件删除,然后设

linux kernel内存回收机制

转:http://www.wowotech.net/linux_kenrel/233.html linux kernel内存回收机制 作者:itrocker 发布于:2015-11-12 20:37 分类:内存管理 无论计算机上有多少内存都是不够的,因而linux kernel需要回收一些很少使用的内存页面来保证系统持续有内存使用.页面回收的方式有页回写.页交换和页丢弃三种方式:如果一个很少使用的页的后备存储器是一个块设备(例如文件映射),则可以将内存直接同步到块设备,腾出的页面可以被重用:如果