PHP的垃圾回收机制主要有两部分组成:引用计数方法和循环引用收集器。
引用计数方法
1、php把每个变量都存在一个叫"zval"的变量容器中。zval变量容器,不仅包含变量的类型和值,还两个字节的额外信息"is_ref"和"refcount"。
- "is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个值,PHP 引擎知道如何区分普通变量和引用。由于 PHP 允许用户自定义引用,通过 & 运算符创建引用,zval 容器还有内部引用计数机制来优化内存使用。
- "refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。
2、"refcount"的计数规则,定义变量是"refcount" 默认为1,当变量被赋值给另一个变量时"refcount" 加1。当使用unset()删除变量时"refcount" 减1,当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束)"refcount" 减1。
3、变量容器在”refcount“变成0时就被销毁。
循环引用收集器
虽然引用计数方法很实用,但是在处理如下情况时会失效:如果你创建了两个对象,并让它们相互引用,即使你将原始的引用全部消除,它们的"refcount"也永远不会降至0(因为它们彼此间一直在引用对方),从而导致内存泄露。例如:
$a = new stdClass();
$b = new stdClass();
$a->child = $b;
$b->parent = $a;
此时,即使 $a
和 $b
两个变量被 unset
,他们指向的对象也不会被销毁,因为它们彼此持有对方的引用,造成了循环引用,引用计数永远不会降到0。
为了解决循环引用的问题,PHP的Zend引擎实现了循环引用收集器。这个机制不会替代传统的引用计数模型,而是作为补充来处理特定情况下的内存泄漏。
当更改变量内容(如赋值、unset等)时,如果变量容器的引用计数不为0,但是达到了可以释放的条件,Zend引擎会将这些容器放入一个特殊的缓冲区中。这个缓冲区不会立即处理,而是在以下任一条件满足时触发垃圾收集周期:
- 缓冲区达到一定大小(可以通过
gc_collect_cycles()
手动触发)。 - 程序终止期间。
垃圾收集周期工作流程如下:
- 标记根缓冲区:检查放在缓冲区内的所有变量容器,如果它们的引用计数为1(只有因为循环引用而没有被销毁),则认为它们是“可能的”根节点。
- 构建根缓冲区:对每个可能的根节点进行遍历,寻找循环引用结构。
- 清理:清除检测到的循环引用,并释放相关资源。
打开和关闭垃圾回收机制的循环引用收集器
可以在php.ini中的zend.enable_gc开启垃圾回收机制的循环引用收集器,也能通过分别调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制的循环引用收集器。
在PHP编程中,关于垃圾回收机制优化代码,可以遵循以下几点注意事项:
-
尽量避免创建大量的长生命周期的对象,如果必要,当不再需要这些对象时,尽可能显式地销毁他们(例如:使用unset()函数)。
-
对于大量的临时数据,试图将其放在函数的局部变量中,并确保它们在函数返回后被清除。
-
避免使用全局变量来存储大量的数据。全局变量通常会在整个脚本执行期间存在,从而增加了内存使用。
-
尽量避免创建循环引用。即使你有一个完美的理由去这么做,也请确保在不再需要这些对象时解除引用。