经过测试,将内存密集型代码拆分成单独的函数可以让垃圾收集工作。
例如,原始代码如下:-
while(true){
//执行内存密集型代码
}
可以变成类似以下内容:-
function intensive($parameters){
//执行内存密集型代码
}
while(true){
intensive($parameters);
}
传统上,引用计数内存机制(如以前 PHP 使用的机制)无法解决循环引用内存泄漏问题;但是,从 5.3.0 开始,PHP 实施了来自 » 引用计数系统中的并发循环收集 论文中的同步算法,该算法解决了该问题。
有关算法工作原理的完整解释将略微超出本节的范围,但这里解释了基本原理。首先,我们必须建立一些基本规则。如果 refcount 增加了,它仍在使用中,因此不是垃圾。如果 refcount 减少并达到零,则可以释放 zval。这意味着垃圾循环只能在 refcount 参数减少到非零值时创建。其次,在垃圾循环中,可以通过检查是否可以将其 refcount 减少一个,然后检查哪些 zval 的 refcount 为零来发现哪些部分是垃圾。
为了避免在每次减少 refcount 时都调用垃圾循环检查,该算法将所有可能的根(zval)放入“根缓冲区”(将其标记为“紫色”)。它还确保每个可能的垃圾根仅在缓冲区中出现一次。只有当根缓冲区已满时,收集机制才会对缓冲区中的所有不同 zval 开始运行。请参阅上图中的步骤 A。
在步骤 B 中,该算法对所有可能的根运行深度优先搜索,以减少它找到的每个 zval 的 refcount,确保不会两次减少同一 zval 的 refcount(通过将其标记为“灰色”)。在步骤 C 中,该算法再次从每个根节点运行深度优先搜索,以再次检查每个 zval 的 refcount。如果发现 refcount 为零,则该 zval 将被标记为“白色”(图中为蓝色)。如果它大于零,则它会从该点开始以深度优先搜索的方式恢复 refcount 的减少,并将它们再次标记为“黑色”。在最后一步(D)中,该算法遍历根缓冲区,从中删除 zval 根,同时检查哪些 zval 在前一步中被标记为“白色”。所有标记为“白色”的 zval 都将被释放。
现在您已经了解了算法的工作原理,我们将回顾它如何与 PHP 集成。默认情况下,PHP 的垃圾收集器已打开。但是,有一个 php.ini 设置允许您更改它:zend.enable_gc。
当垃圾收集器打开时,上述循环查找算法将在根缓冲区已满时执行。根缓冲区具有 10,000 个可能根的固定大小(虽然您可以通过更改 PHP 源代码中 Zend/zend_gc.c
中的 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);
}
── 未使用的对象 ─── ─ 使用中的对象
↓ ↓ ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
▲ ▲
无引用的 对象 引用了的 对象
对象 对象
█ 内存泄漏