PHP中的垃圾回收器是如何工作的?

4

我有一个 PHP 脚本,其中包含一个大型的人员数组,它通过 SOAP 从外部资源获取其详细信息,修改数据并发送回来。由于详细信息的大小,我将 PHP 的内存升级到了 128MB。运行约 4 小时后(可能需要 4 天才能完成),它耗尽了内存。以下是它的基本功能:

$people = getPeople();
foreach ($people as $person) {
    $data = get_personal_data();
    if ($data == "blah") {
        importToPerson("blah", $person);
    } else {
        importToPerson("else", $person);
    }
}

运行过程中出现内存耗尽并崩溃后,我决定在foreach循环之前初始化$data,并根据top的显示,该进程的内存使用率未超过7.8%,已持续运行12小时。
那么我的问题是,即使重复使用,PHP是否不会对在循环内部初始化的变量运行垃圾收集器?系统是否正在回收内存,并且PHP尚未将其标记为可用,最终会再次崩溃(我现在已将其增加到256MB,因此我更改了2个设置,但不确定哪个修复了问题,我可以更改脚本以回答这个问题,但不想再等待12个小时才能崩溃)?
我没有使用Zend框架,因此我认为与此类似的其他问题并不相关。
编辑:实际上,我没有关于脚本或其功能的问题。目前,就所有系统报告而言,我并没有任何问题。这个问题是关于垃圾收集器及其如何/何时在foreach循环中回收资源以及/或者系统如何报告php进程的内存使用情况。

3
我很想知道为什么这个已经被踩了两次... - Moses
importToPerson() 函数中会发生什么? - PeeHaa
1
if ($data = "blah") { 应该改为 if ($data == "blah") { 吗? - PeeHaa
http://php.net/manual/en/features.gc.php - dqhendricks
如果你的脚本运行时出现内存不足的情况,很可能是脚本中有一些创建了新对象或标量,但从未被清空或重用。或者,你正在使用旧版本的PHP。你可以使用XDebug来查看问题所在。 - dqhendricks
显示剩余10条评论
2个回答

3
我不了解PHP虚拟机的内部情况,但从我的经验来看,在您的页面运行时它不会进行垃圾回收。这是因为当它完成时,它会丢弃您的页面创建的所有内容。
大多数情况下,当页面耗尽内存并且限制相当高(128Mb不算高)时,存在算法问题。许多PHP程序员组合数据结构,然后将其传递到下一步,该步骤迭代该结构,通常创建另一个结构。重复多次。不幸的是,这种方法会消耗大量内存,并且您最终会在内存中创建多个数据副本。 PHP 5 中的两个真正的大变化是对象是引用计数而不是复制,整个字符串子系统也变得更加快速。但这仍然是一个问题。
为了最小化内存使用,您应该重新设计算法,使其可以从头到尾使用一块数据。然后你继续下一个并重新开始。最好的情况是你永远不会在内存中拥有整个数据集。对于基于数据库的网站,这意味着在获取下一个之前,处理来自数据库查询的一行数据直到展示。当然,这种方法并不总是可行的,脚本只需保留大量的数据。
话虽如此,您可以对部分数据执行此节省内存的方法。诀窍是在循环结束时明确 unset() 一个或两个关键变量。这应该可以释放空间。另一种“最佳实践”技巧是将不需要在循环中进行的数据操作移出循环。就像您似乎已经发现的那样。
我运行过需要超过1GB内存的PHP脚本。您实际上可以通过 ini_set('memory_limit', '1G'); 设置每个脚本的内存限制。

3
PHP 5.3 添加了一个“真正的”垃圾收集器。虽然还不完美,但比您所描述的要好一些。 - user149341
它实际上是在命令行上运行的。虽然我考虑过每次处理一行(而不是所有行并迭代返回的数组),但我觉得额外的数据库查询会抵消任何好处。 - Rudiger
1
有一个巨大的循环来处理每一行是可行的,但是当你仍在获取上一个结果集时尝试执行新查询时,容易出现资源问题。此外,有时候做许多小的SQL查询比做少量更重的查询更快。 - staticsan
PHP循环垃圾收集器的自动触发器并不聪明,因此如果您执行某些操作可能会导致大型对象相互引用而没有任何引用来源,那么您应该在可能泄漏内存后调用gc_collect_cycles()(http://php.net/manual/en/function.gc-collect-cycles.php)。这会导致一些额外的CPU使用,但它将减少内存使用。否则,PHP可能会在未来清理垃圾,并且在此期间,您的内存使用量比预期高得多。 - Mikko Rantalainen

1
使用memory_get_usage()来查看发生了什么?可以将其放在循环内部以查看内存分配的行为。 您是否尝试过查看系统监视器或其他工具,以查看php在该过程中使用了多少内存?

目前不想修改脚本,因为我们必须从头开始重新启动(已经浪费了一天时间)。顶部是系统监视器,内存使用率还没有超过7.8%,所以理论上不会再有更多的内存分配,脚本也不应该会耗尽内存。 - Rudiger
你不能使用这些修改运行另一个带有迭代次数限制的脚本吗?也就是说,我不完全确定你在做什么。 - Norm
这个脚本实际上非常耗费资源,因为它执行了很多SOAP请求/数据库操作。如果我无法得到答案,我将稍后进行调查,但可能需要一天的时间来更改脚本,运行一个小时左右,监视内存分配情况,反复洗涤和重复。希望有比我更了解PHP垃圾收集器的人能够提供一些见解。 - Rudiger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接