PHP XML内存泄漏?

5
我们的一项常规脚本存在严重的内存泄漏问题,会很快耗尽服务器上的可用内存。虽然我已经花费了很多时间进行研究和实验,但是我仍然无法解决这个问题。
以下是代码:
    echo '1:'.memory_get_usage()."\n";
ini_set('memory_limit', '1G');
    echo '2:'.memory_get_usage()."\n";

$oXML = new DOMDocument();
    echo '3:'.memory_get_usage()."\n";
$oXML->load('feed.xml'); # 556 MB file
    echo '4:'.memory_get_usage()."\n";

$xpath = new DOMXPath($oXML);
    echo '5:'.memory_get_usage()."\n";
$oNodes = $xpath->query('//feed/item'); # 270,401 items
    echo '6:'.memory_get_usage()."\n";

unset($xpath);
    echo '7:'.memory_get_usage()."\n";
unset($oNodes);
    echo '8:'.memory_get_usage()."\n";
unset($oXML);
    echo '9:'.memory_get_usage()."\n";

这里是输出结果:

1:679016
2:679320
3:680128
4:680568
5:681304
6:150852408
7:150851840
8:34169968
9:34169448

正如您所看到的,当我们使用xpath将节点加载到对象中时,内存使用量从681,304跳升到了150,852,408。我并不太担心这个。
我的问题是,即使在销毁$oNodes对象之后,我们仍然停留在34,169,968的内存使用量上。
但是,真正的问题是PHP显示的内存使用量只是脚本所消耗总内存的一小部分。直接从服务器命令行使用“free -m”,我们从3,295 MB的内存使用量增加到5,226 MB,而且它永远不会回落。每次运行此脚本,我们就会损失2 GB的内存,我完全不知道为什么或如何解决它。
我尝试使用SimpleXML,但结果基本相同。我还研究了这三个线程,但没有找到任何有用的东西:

DOMDocument / Xpath在长时间命令行进程中泄漏内存 - 有没有方法来解构这个类?

DOMDocument PHP内存泄漏

我希望这是我忽略的一些容易解决的问题。

更新11/10:看起来内存最终会被释放。我注意到在30分钟左右,突然又释放了一个大块。然而,最近这还不够快,无法防止服务器耗尽内存并锁定。

值得一提的是,我们正在Red Hat 5.11上以Apache 2.2.3和PHP 5.3.15运行。我们正在努力更新所有这些的最新版本,因此在升级路径的某个地方,我们可能会发现这个问题已经被修复了。不过最好在那之前解决它。


在使用unset之前,请尝试查看$oNodes有多少个引用。参见:http://php.net/manual/zh/features.gc.refcounting-basics.php - Machavity
@Marc B:在这种情况下,“child”是什么意思我不确定。对不起。在这种情况下,此脚本仅运行一次。它永远不会被同时运行两个不同的进程所影响。 - Shane Pike
这是一个命令行 PHP 脚本吗?无论它占用多少内存,在脚本退出时都会被释放。它在退出后可能“泄漏”内存的唯一方式是,如果它正在滥用某些系统功能并且该功能存在泄漏。 - Marc B
@Marc B:对吧?这正是我所期望的。脚本运行,结束,内存释放。然而,实际情况并非如此,并且memory_get_usage()也没有完全说明问题。 - Shane Pike
2
PHP 不会仅仅因为您取消设置一个变量就运行垃圾收集器。GC 运行的计算代价非常昂贵,所以 PHP 只有在必须时才会运行它,例如内存变得紧张。因此,memory_get_usage() 并不是一个真正有效的测试。 - Marc B
显示剩余6条评论
2个回答

0

最近我遇到了一个和你类似的问题。我们需要从一个3GB的XML文件中提取数据,同时也注意到服务器内存正在接近极限。有几种方法可以减少内存使用:

  • 不要使用XPath,因为它会导致大量的内存使用,而是使用(例如)file_get_contents。然后通过正则表达式搜索找到所需的数据
  • 将XML拆分成较小的部分。基本上这是重新发明XML文件,但是您可以处理文件的最大大小(因此内存)

您提到在30分钟后释放了一些内存。在30分钟内读取500MB的XML太慢了。我们使用的解决方案是将3GB的XML文件分成几个部分(约200个)。我们的脚本在不到5分钟的时间内将所需的数据(约700k条记录)写入我们的数据库。


0
我们刚刚遇到了一个类似的问题,与使用DomDocument的PHPDocxPro有关,并向他们提交了一个补丁,至少改善了这个问题。通过get_memory_usage()报告的内存使用量从未增加,就好像PHP根本没有意识到分配的内存一样。我们更关心的是通过top或ps观察执行时报告的内存使用情况。
// ps reports X memory usage
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + Y memory usage
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + ~2Y memory usage
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + ~3Y memory usage

在每个后续调用之前添加一个unset()...
// ps reports X memory usage
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + Y memory usage
unset($foo);
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + ~Y memory usage
unset($foo);
var $foo = (new DomDocument())->loadXML(getSomeXML());
// ps reports X + ~Y memory usage

我还没有深入挖掘扩展代码以了解发生了什么,但我的猜测是他们在分配内存时没有使用PHP的内存分配方式,因此它不被视为 get_memory_usage() 所考虑的堆的一部分。尽管如此,似乎有一些引用计数来确定是否可以释放内存。在后续调用之前的 unset($foo) 确保扩展可以重复使用某些资源。如果没有这样做,每次运行代码时都会增加内存使用量。


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