测试后,将内存密集型代码分解成单独的函数可以使垃圾回收工作。
例如,原始代码如下所示:
while(true){
//执行内存密集型代码
}
可以转换成如下所示:
function intensive($parameters){
//执行内存密集型代码
}
while(true){
intensive($parameters);
}
传统上,引用计数内存机制(例如以前 PHP 使用的机制)无法解决循环引用内存泄漏问题;但是,从 5.3.0 开始,PHP 实现来自» 引用计数系统中的并发循环收集论文中的同步算法,该算法解决了这个问题。
对该算法工作原理的完整解释略微超出本节的范围,但此处解释了基础知识。首先,我们必须建立一些基本规则。如果引用计数增加,它仍在使用中,因此不是垃圾。如果引用计数减少并达到零,则可以释放 zval。这意味着只有当引用计数参数减少到非零值时,才能创建垃圾循环。其次,在垃圾循环中,可以通过检查是否可以将其引用计数减少一个,然后检查哪些 zval 的引用计数为零来发现哪些部分是垃圾。
为了避免在每次可能减少引用计数时都必须调用垃圾循环检查,该算法而是将所有可能的根(zval)放入“根缓冲区”(将其标记为“紫色”)。它还确保每个可能的垃圾根只在缓冲区中出现一次。只有当根缓冲区已满时,才会针对其中的所有不同 zval 开始收集机制。请参见上图中的步骤 A。
在步骤 B 中,该算法对所有可能的根运行深度优先搜索,以减少找到的每个 zval 的引用计数,确保不会两次减少同一 zval 的引用计数(通过将其标记为“灰色”)。在步骤 C 中,该算法再次从每个根节点运行深度优先搜索,以再次检查每个 zval 的引用计数。如果发现引用计数为零,则将 zval 标记为“白色”(图中为蓝色)。如果大于零,则从该点开始使用深度优先搜索撤消引用计数减少一个,并将它们再次标记为“黑色”。在最后一步 (D) 中,该算法遍历根缓冲区,从中删除 zval 根,同时检查在上一步中哪些 zval 已标记为“白色”。每个标记为“白色”的 zval 都将被释放。
现在您已经了解了该算法的工作原理,我们将回顾一下它如何与 PHP 集成。默认情况下,PHP 的垃圾收集器已启用。但是,有一个php.ini设置允许您更改此设置:zend.enable_gc。
启用垃圾收集器时,每当根缓冲区已满时,就会执行如上所述的循环查找算法。根缓冲区具有 10,000 个可能的根的固定大小(尽管您可以通过更改Zend/zend_gc.c
中PHP源代码中的GC_THRESHOLD_DEFAULT
常量并重新编译PHP来更改此大小)。关闭垃圾收集器时,循环查找算法将永远不会运行。但是,无论是否使用此配置设置激活垃圾收集机制,都始终会在根缓冲区中记录可能的根。
如果在关闭垃圾收集机制时根缓冲区中充满了可能的根,则将不再记录其他可能的根。那些未记录的可能的根将永远不会被算法分析。如果它们是循环引用循环的一部分,则它们将永远不会被清理,并将导致内存泄漏。
即使禁用了该机制,也记录可能的根的原因是,记录可能的根比每次可能找到可能的根时都必须检查该机制是否已启用要快。但是,垃圾收集和分析机制本身可能需要相当长的时间。
除了更改zend.enable_gc配置设置外,还可以通过调用gc_enable()或gc_disable()分别启用和禁用垃圾收集机制。调用这些函数的效果与使用配置设置启用或禁用该机制相同。即使可能的根缓冲区尚未满,也可以强制收集循环。为此,您可以使用gc_collect_cycles()函数。此函数将返回算法收集的循环数量。
能够自己启用和禁用该机制并启动循环收集的原因是,应用程序的某些部分可能对时间非常敏感。在这些情况下,您可能不希望垃圾收集机制启动。当然,通过关闭应用程序某些部分的垃圾收集,您确实有创建内存泄漏的风险,因为某些可能的根可能不适合有限的根缓冲区。因此,在调用gc_disable()之前调用gc_collect_cycles()可能是明智之举,以便释放可能由于已记录在根缓冲区中的可能的根而丢失的内存。然后,这将留下一个空缓冲区,以便在循环收集机制关闭时有更多空间来存储可能的根。
测试后,将内存密集型代码分解成单独的函数可以使垃圾回收工作。
例如,原始代码如下所示:
while(true){
//执行内存密集型代码
}
可以转换成如下所示:
function intensive($parameters){
//执行内存密集型代码
}
while(true){
intensive($parameters);
}
── 未使用的对象 ─── ─ 使用中的对象
↓ ↓ ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
▲ ▲
未引用的 已引用的
对象 对象
█ 内存泄漏