一看就懂系列之 由浅入深聊一聊php的垃圾回收机制 (3)

产生内存泄漏主要真凶:环形引用。 
现在来造一个环形引用的场景:

<?php $a = ['one']; $a[] = &$a; xdebug_debug_zval('a');

1

2

3

4

得到:

a: (refcount=2, is_ref=1), array (size=2) 0 => (refcount=1, is_ref=0),string 'one' (length=3) 1 => (refcount=2, is_ref=1), &array<

1

2

3

4

5

6

这样 $a数组就有了两个元素,一个索引为0,值为one字符串,另一个索引为1,为$a自身的引用。

图1

此时删掉$a:

<?php $a = ['one']; $a[] = &$a; unset($a);

1

2

3

4

图2

如果在小于php5.3的版本就会出现一个问题:$a已经不在符号表了,没有变量再指向此zval容器,用户已无法访问,但是由于数组的refcount变为1而不是0,导致此部分内存不能被回收从而产生了内存泄漏。

5.3版本以后php是如何处理垃圾内存的? 判断处理过程

为解决环形引用导致的垃圾,产生了新的GC算法,遵守以下几个基本准则:

1.如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾

2.如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾

3.如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾

are you ok?

来个白话文版:就是对此zval中的每个元素进行一次refcount减1操作,操作完成之后,如果zval的refcount=0,那么这个zval就是一个垃圾

引用php官方手册的配图:

图3

A:为了避免每次变量的refcount减少的时候都调用GC的算法进行垃圾判断,此算法会先把所有前面准则3情况下的zval节点放入一个节点(root)缓冲区(root buffer),并且将这些zval节点标记成紫色,同时算法必须确保每一个zval节点在缓冲区中之出现一次。当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。

B:当缓冲区满了之后,算法以深度优先对每一个节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。

C:算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)

D:遍历zval节点,将C中标记成白色的节点zval释放掉。

are you ok?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzfggx.html