PHP版本引起的GC机制变动部分解析

起因:在刷面经的时候有这么一个题目 “PHP的垃圾回收机制”,第一反应就是PHP使用引用计数这样的机制来判断一个变量是否是垃圾而out掉它,网上参考了下面这篇文章

PHP7垃圾回收机制详解 https://m.php.cn/topic/php7/425508.html

里边有这么一段代码

#官方例子
$a = 1;
$b = $a;
xdebug_debug_zval('a');
$a =10;
xdebug_debug_zval('a');
unset($a);
xdebug_debug_zval('a');
#结果
a:(refcount=2, is_ref=0)=1
a:(refcount=1, is_ref=0)=10
a: no such symbol

本质严谨治学的态度,使用本机PHP(PHP7.3.4)运行,结果如下:

解释一下:refcount 为指向该变量容器的数量统计,is_ref为引用当前变量容器的数量统计,当给一个变量赋值时,$b = arefcount++a,refcount ++,a,refcount++,b = &$a,refcount ++,is_ref++

a: (refcount=0, is_ref=0)=1
a: (refcount=0, is_ref=0)=10
a: no such symbol

???这是问什么呢,明明标题《PHP7垃圾回收机制详解》,卖假药?

PHP7.2.9运行结果:

a: (refcount=0, is_ref=0)=1
a: (refcount=0, is_ref=0)=10
a: no such symbol

PHP7.0.9运行结果:

a: (refcount=0, is_ref=0)=1
a: (refcount=0, is_ref=0)=10
a: no such symbol

PHP5.3.29运行结果:

a: (refcount=2, is_ref=0)=1
a: (refcount=1, is_ref=0)=10
#该版本中,如果变量被注销,使用xdebug_debug_zval()函数将无输出

PHP5.2.x版本GC机制与更高版本不同,只是使用了简单的计数,在这里不予以讨论

在正常的理解范围内,PHP5.3版本是容易被理解的,当给一个变量赋值时,refcount=1,当使用值传递的方式赋值时,refcount=2,但是PHP7.x版本中发生了什么变化呢?

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:
IS_LONG
IS_DOUBLE
当然对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:
IS_NULL
IS_FALSE
IS_TRUE

所以这个谜团可以解开了,在PHP7.x版本中,对于这些类型的数据是不走引用计数了的,在结果中还可以看出,对于变量的值传递方式赋值,使用了COW(copy on write)机制,对于普通的赋值,PHP直接将两个变量指向同一个内存地址,只有当其中一个值发生变化时,才会开辟新的内存空间用来存放改动变量的值

运行环境:PHP5.3.9
#值传递
$a = 1;
$b = $a;
xdebug_debug_zval('a');
$a =10;
xdebug_debug_zval('a');
unset($a);
xdebug_debug_zval('a');
#结果
a:(refcount=2, is_ref=0)=1
a:(refcount=1, is_ref=0)=10
a: no such symbol
#引用传递
$a = 1;
$b = &$a;
xdebug_debug_zval('a');
$a =10;
xdebug_debug_zval('a');
unset($a);
xdebug_debug_zval('a');
#结果
a:(refcount=2, is_ref=1)=1
a:(refcount=2, is_ref=1)=10
a: no such symbol

再举一个例子:

#代码
$a = array(1,'one');
xdebug_debug_zval('a');
#PHP5.3.29结果
a: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)=1, 1 => (refcount=1, is_ref=0)='one')
#PHP7.0.9结果
a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=2, is_ref=0)='one')
#PHP7.2.9结果
a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=1, is_ref=0)='one')
#PHP7.3.4结果
a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=1, is_ref=0)='one')

可见,PHP7.2.x以前,对于一个定义的数组,其refcount为1,而以后的版本refcount为2,PHP数组开发人员有这么个说法

For arrays the not-refcounted variant is called an “immutable array”. If you use opcache, then constant array literals in your code will be converted into immutable arrays. Once again, these live in shared memory and as such must not use refcounting. Immutable arrays have a dummy refcount of 2, as it allows us to optimize certain separation paths.

牵扯到 PHP7.2.x 中的另一个概念,叫做 immutable array (不可变数组),在不可变数组下,使用一个伪计数值2;

类型是这种直接申明规定数组array(1,2,3)的refcount = 2 你可以看下 array(“time”=>time())这样的申明,就是1

对于常量数组
#PHP7.0.9
#PHP5.3.29
a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)
#PHP7.3.4
#PHP7.2.9
a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)
对于非常量数组
#PHP7.3.4
#PHP7.2.9
#PHP7.0.9
#PHP5.3.29
a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1582285249)

而对于代码的数组中存放字符串,只有PHP7.0.9运行结果显示refcount为2,可能是在大版本升级中残留的待优化部分

循环引用

#例子
$a = array(1);
$a[1] = &$a;
xdebug_debug_zval('a');
unset($a);
xdebug_debug_zval('a');
#结果
a: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)=1, 1 => (refcount=2, is_ref=1)=...)

该变量在内存中的表示:

PHP版本引起的GC机制变动部分解析

循环引用造成的内存泄漏

为了清理这些垃圾,引入了两个准则

  • 如果引用计数减少到零,所在变量容器将被清除(free),不属于垃圾
  • 如果一个zval 的引用计数减少后还大于0,那么它会进入垃圾周期。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。

循环引用基本上只会出现在 数组和对象中,对象是因为它的本身就是引用

object和array的回收过程

PHP7的垃圾回收包含两个部分,一个是垃圾收集器,一个是垃圾回收算法。

垃圾收集器,把刚刚提到的,可能是垃圾的元素收集到回收池中 也就是把变量的 zend_refcount>0的变量 放在回收池中。 当回收池的值达到一定额度了,会进行统一遍历处理。进行模拟删除,如果zend_refcount=0那就认为是垃圾,直接删除它。

遍历回收池中的每一个变量,根据每一个变量,再遍历每一个成员,如果成员还有嵌套的话继续遍历。然后把所有成员的 做模拟的 refcount -1。如果此时外部的变量的 引用次数为 0 。那么可以视为垃圾清除。如果大于0,那么恢复引用次数,并从垃圾回收池中取出。

参考:

https://blog.csdn.net/qq_36172443/article/details/82291385

https://blog.csdn.net/black_ox/article/details/50341225

https://ask.csdn.net/questions/706390

PHP版本引起的GC机制变动部分解析PHP版本引起的GC机制变动部分解析 换你睡床右边 发布了13 篇原创文章 · 获赞 9 · 访问量 3513 私信 关注
上一篇:利用PHPStudy搭建Xdebug调试环境


下一篇:vscode+php+xdebug Time-out connecting to client (Waited: 200 ms)