PHP垃圾回收
语言分类
1、从类型的维度来看,编程语言可以分为三大类:
① 静态类型语言,比如:C/Java等,在静态语言类型中,类型的检查是在编译期(compile-time)确定的, 也就是说在运行时变量的类型是不会发生变化的。
② 动态类型语言,比如:PHP,python等各种脚本语言,这类语言中的类型是在运行时确定的, 那么也就是说类型通常可以在运行时发生变化
③ 无类型语言,,比如:汇编语言,汇编语言操作的是底层存储,他们对类型毫无感知。
2、从变量定义角度可以分两类:
① 强类型:一旦某个变量被申明为某个类型的变量,在程序运行过程中,就不能将该变量的类型以外的值赋予给它 (当然并不完全如此,这可能会涉及到类型的转换)
② 弱类型:一个变量可以表示任意的数据类型。PHP及Ruby,JavaScript等脚本语言属于弱类型语言
PHP的垃圾回收
每个php变量存在一个叫”zval”的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息
struct _zval_struct { zvalue_value value; //变量value值 zend_uint refcount__gc; //引用计数内存中使用次数,为0删除该变量 zend_uchar type; //变量类型 zend_uchar is_ref__gc; //区分是否是引用变量 };
字段说明:
is_ref:是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用
应用: &引用赋值时,原变量的is_ref 变为1,refcount 加1. 如果给一个变量&赋值,之前 = 赋值的变量会分配空间
refcount__gc:用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域
应用:将一个变量 = 赋值给另一个变量时,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。当然,如果unset原变量,新变量直接就使用原变量的zval而不是重新分配
Type:PHP在声明或使用变量的时候,并不需要显式的指明数据类型,但是并不表示没有数据类型,PHP的数据类型分为三大类8中类型:标量(boolean、integer、float、string),复合类型(array,object),特殊类型(resource,null),
应用:type的值可以是: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一
is_ref 和 refcount关系:有一个默认关系,当refcount值为1时,is_ref的值为false。因为refcount为1,此变量不可能有多个别名,也就不存在引用了
环装引用问题
在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount为0,那么变量的空间可以被释放,否则就不释放,这是一种非常简单的GC实现,现在unset ($a),那么array的refcount减1变为1.现在无任何变量指向这个zval,而且这个zval的计数器为1,不会回收
1、内存泄漏
如图,不再有某个作用域中的任何符号指向这个结构(就是变量容器),但是由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏
2、解决方案
PHP5.3中,采用了专门的算法(比较复杂)来解决这个环装引用导致的内存泄露问题:
当一个zval可能为垃圾时,回收算法会把这个zval放入一个内存缓冲区。当缓冲区达到最大临界值时(最大值可以设置),回收算法会循环遍历所有缓冲区中的zval,判断其是否为垃圾,并进行释放处理。或者我们在脚本中使用gc_collect_cycles,强制回收缓冲区中的垃圾
3、垃圾确认
① 如果一个zval的refcount增加,那么此zval还在使用,肯定不是垃圾,不会进入缓冲区
② 如果一个zval的refcount减少到0, 那么zval会被立即释放掉,不属于GC要处理的垃圾对象,不会进入缓冲区。
③ 如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,将其放入缓冲区。PHP5.3中的GC针对的就是这种zval进行的处理,即产生回收周期
开启/关闭垃圾回收机制可以通过修改php配置实现,也可以在程序中使用gc_enable() 和 gc_disable()开启和关闭,但是开启垃圾回收机制后,针对内存泄露的情况,可以节省大量的内存空间,但是由于垃圾回收算法运行耗费时间,开启垃圾回收算法会增加脚本的执行时间,相对于不开启的时候,脚本执行时间增加了7%
垃圾回收周期及三色标记法
1、要处理的问题
处理无法处理循环的引用内存泄漏问题
2、产生周期条件
先要建立一些基本规则,如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。如果引用计数减少到零,所在变量容器将被清除(free)。就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期(garbage cycle)。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾
3、触发回收条件
当垃圾回收机制打开时,每当根缓存区存满时,就会执行循环查找算法。根缓存区有固定大小,默认10000个可能根,也可以修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES。
4、三色标记过程
① 步骤A:为避免不得不检查所有引用计数可能减少的垃圾周期,这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记,称为疑似垃圾),这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作
② 步骤B:模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减”1″,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰
③ 步骤C:模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉
引用文章:PHP垃圾回收周期