PHP

PHP垃圾回收机制

Posted by Liao on 2020-04-24

PHP5.3以前 引用计数机制

PHP5.3之前使用过内存回收算法Reference Counting 引用计数

其思想是,为内存对象分配一个计数器,当内存对象建立时计数器初始化为1。(如$a = "hello world"

以后每一个新变量引用该变量,(如赋值$a = $b),计数器加1;当减少引用次内存对象(unset($a)),计数器减1。

把一个变量赋值给另一变量将增加引用次数(refcount)

1
2
3
4
5
6
7
<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>

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

当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束),或对变量调用了函数 unset()时,refcount就会减1

1
2
3
4
5
6
7
8
9
10
<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
?>

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

缺点

引用计数虽然简单,但是会有一个致命的缺陷,在循环引用的时候会造成内存泄露

1
2
3
4
5
6
7
8
9
10
11
12
<?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)=...
)
*/

因为 refcount = 1 不等于 0,所以它不会被当作垃圾回收。但同时在符号表中找不到哪个符号指向这个 zval,导致用户也没有办法手动清除。最终的结果就是在这个脚本结束之前,这个结构会一直占用内存,导致内存泄漏。

PHP5.3以后 回收周期

算法思想:深搜遍历

1.把所有可能根(possible roots或者疑似垃圾,都是zval变量容器),放在**根缓冲区(root buffer)中(这样可以同时确保每个可能的垃圾根(possible garbage root)**在缓冲区中只出现一次),仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。

2.模拟删除根缓冲区的疑似垃圾,每个变量只能被模拟删除一次。模拟删除时可能将不是疑似垃圾的普通变量引用数减”1”,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。

3.模拟恢复疑似垃圾。恢复的条件是,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次。这样剩下的一堆没能恢复的就是该删除的节点了,在步下一步中遍历出来真的删除掉。

PHP5.3算法特性:

并不是每一次refcount减少都进入回收周期,当根缓冲区满了才开始垃圾回收