跳转至

PHP GC 相关资料

在传统的Web开发的情况下,PHP程序员通常不需要考虑 PHP GC 相关的问题,因为分配的内存会在 Request 结束后自动释放。但如果你要编写需要长期运行的 worker daemon 或者类似的 cli 程序,则了解 PHP GC 相关内容还是有用的。

PHP 引用计数

PHP 的变量在底层通过 zval 实现,每个变量都会包含在一个 zval 中;zval 除了包含变量相关的信息外还会包含两个额外的字段:is_ref 是一个布尔值,指示该变量是否在一个引用集中;refcount 标识有多少个变量名称指向该 zval。

可以使用 xdebug_debug_zval() 函数来查看 zval 相关信息。

如:

$a = "hello";
$b = $a;
$c = &$a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
unset($a);
xdebug_debug_zval('b');
xdebug_debug_zval('c');

输出:

a: (refcount=2, is_ref=1)='hello'
b: (interned, is_ref=0)='hello'
c: (refcount=2, is_ref=1)='hello'
b: (interned, is_ref=0)='hello'
c: (refcount=1, is_ref=1)='hello'

将一个变量赋值给另一个变量,会增加其 refcount;如果一个变量超出其作用域,或者使用 unset(), 则其 refcount 会相应减少;

$a = "hello";
$b = [$a];
xdebug_debug_zval('a');
xdebug_debug_zval('b');
$c = new \stdclass;
$c->world = $b;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
unset($b);
xdebug_debug_zval('a');
xdebug_debug_zval('c');

输出:

a: (interned, is_ref=0)='hello'
b: (refcount=1, is_ref=0)=array (0 => (interned, is_ref=0)='hello')
a: (interned, is_ref=0)='hello'
b: (refcount=2, is_ref=0)=array (0 => (interned, is_ref=0)='hello')
c: (refcount=1, is_ref=0)=class stdClass { public $world = (refcount=2, is_ref=0)=array (0 => (interned, is_ref=0)='hello') }
a: (interned, is_ref=0)='hello'
c: (refcount=1, is_ref=0)=class stdClass { public $world = (refcount=1, is_ref=0)=array (0 => (interned, is_ref=0)='hello') }

当 refcount=0 时,该变量的内存就会被回收。但引用计数有个缺陷,就是无法检测循环引用。

PHP GC

针对循环引用引发的内存泄露问题,PHP 5.3 开始引入回收周期。将可能存在的内存泄漏放到一个 roots 中,当roots满了后就进行垃圾回收周期。

参考资料

XDebug Garbage Collection Statistics What Is Garbage Collection in PHP And How Do You Make The Most Of It? php底层原理之垃圾回收机制 PHP 的垃圾回收机制-理解PHP如何解决循环引用导致的内存泄漏问题