php变量的内部存储结构
php是由C编写而成的,所以php变量的内部存储结构也会和C语言相关,即zval的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct _zval_struct { union { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; };
|
从上面结构体内容可以看出每一个php变量都会由变量类型
、value值
、引用计数次数
和是否是引用变量
四部分组成
引用计数原理
变量容器
非array和object变量,每次将常量赋值给一个变量时,都会产生一个变量容器
举例:
1 2
| $a = 'test'; xdebug_debug_zval('a')
|
结果:
1
| a: (refcount=1, is_ref=0)='test'
|
array和object变量,会产生元素个数+1的变量容器
举例:
1 2 3 4 5 6
| $b = [ 'name' => 'test', 'number' => 3 ]; xdebug_debug_zval('b')
|
结果:
1 2
| b: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='test', 'number' => (refcount=1, is_ref=0)=3)
|
赋值原理(写时复制技术)
从内存角度思考变量之间的赋值
举例:
1 2 3 4 5 6 7 8
| $a = [ 'name' => 'test', 'number' => 3 ]; $b = $a; xdebug_debug_zval('a', 'b'); $b['name'] = 'test1'; xdebug_debug_zval('a', 'b');
|
结果:
1 2 3 4
| a: (refcount=2, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='test', 'number' => (refcount=1, is_ref=0)=3) b: (refcount=2, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='test', 'number' => (refcount=1, is_ref=0)=3) a: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='test', 'number' => (refcount=1, is_ref=0)=3) b: (refcount=1, is_ref=0)=array ('name' => (refcount=1, is_ref=0)='test1', 'number' => (refcount=1, is_ref=0)=3)
|
所以,当变量a赋值给变量b的时候,并没有立刻生成一个新的变量容器,而是将变量b指向了变量a指向的变量容器,即内存”共享”;而当变量b其中一个元素发生改变时,才会真正发生变量容器复制,这就是写时复制技术
引用计数清0
当变量容器的ref_count计数清0时,表示该变量容器就会被销毁,实现了内存回收,这也是php5.3版本之前的垃圾回收机制
举例:
1 2 3 4 5 6
| $a = "test"; $b = $a; xdebug_debug_zval('a'); unset($b); xdebug_debug_zval('a');
|
结果:
1 2
| a: (refcount=2, is_ref=0)='test' a: (refcount=1, is_ref=0)='test'
|
循环引用引发的内存泄露问题
但是php5.3版本之前的垃圾回收机制存在一个漏洞,即当数组或对象内部子元素引用其父元素,而此时如果发生了删除其父元素的情况,此变量容器并不会被删除,因为其子元素还在指向该变量容器,但是由于所有作用域内都没有指向该变量容器的符号,所以无法被清除,因此会发生内存泄漏,直到该脚本执行结束。
举例:
1 2 3 4
| $a = array( 'one' ); $a[] = &$a; xdebug_debug_zval( 'a' );
|
由于该示例不好输出结果,用图表示,如图:
举例:
1 2
| unset($a); xdebug_debug_zval('a');
|
如图:
新的垃圾回收机制
php5.3版本之后引入根缓冲机制,即php启动时默认设置指定zval数量的根缓冲区(默认是10000),当php发现有存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置文件中的指定数量(默认是10000)后,就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题。
确认为垃圾的准则
1、如果引用计数减少到零,所在变量容器将被清除(free),不属于垃圾
2、如果一个zval 的引用计数减少后还大于0,那么它会进入垃圾周期。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。
总结
垃圾回收机制:
1、以php的引用计数机制为基础(php5.3以前只有该机制)
2、同时使用根缓冲区机制,当php发现有存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置文件中的指定数量后,就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题(php5.3开始引入该机制)