强制释放PHP中的内存

时间:2010-03-17 11:23:16

标签: php garbage-collection

在PHP程序中,我按顺序读取一堆文件(file_get_contents),gzdecodejson_decode结果,分析内容,将大部分内容丢弃,以及在阵列中存储大约1%。

不幸的是,随着每次迭代(我遍历包含文件名的数组),似乎有一些内存丢失(根据memory_get_peak_usage,每次约2-10 MB)。我对我的代码进行了双重和三重检查;我没有在循环中存储不需要的数据(并且所需的数据总体上不超过大约10MB),但我经常重写(实际上,数组中的字符串)。显然,PHP没有正确释放内存,因此使用越来越多的RAM直到它达到极限。

有没有办法强制垃圾收集?或者,至少,找出内存的使用位置?

8 个答案:

答案 0 :(得分:37)

它与内存碎片有关。

考虑两个字符串,连接到一个字符串。每个原件必须保留,直到创建输出。输出比任一输入都长 因此,必须进行新的分配以存储这种串联的结果。原始字符串被释放但它们是小块的内存 在'str1' . 'str2' . 'str3' . 'str4'的情况下,每个都会创建几个临时值。 - 并且没有一个适合被释放的空间。由于存储器的其他用途,字符串可能不会在连续的内存中布局(即,每个字符串都是,但各种字符串不是端对端放置)。因此释放字符串会产生问题,因为空间无法有效地重用。所以你随着你创造的每个tmp而成长。而且你永远不会重复使用任何东西。

使用基于数组的内爆,只创建1个输出 - 正好是您需要的长度。仅执行1次额外分配。因此它的内存效率更高,并且不会受到串联碎片的影响。 python也是如此。如果需要连接字符串,则多个连接应始终基于数组:

''.join(['str1','str2','str3'])

在python中

implode('', array('str1', 'str2', 'str3'))
PHP中的

sprintf等价物也没问题。

memory_get_peak_usage报告的内存基本上总是它必须使用的虚拟地图中的“最后”内存位。因此,由于它一直在增长,它报告了快速增长。因为每个分配都落在当前使用的内存块的“末尾”。

答案 1 :(得分:19)

在PHP> = 5.3.0中,您可以调用gc_collect_cycles()强制GC通过。

注意:您需要在启用zend.enable_gc时启用php.ini,或致电gc_enable()以激活循环参考收集器。

答案 2 :(得分:12)

找到解决方案:这是一个字符串连接。我通过连接一些变量逐行生成输入(输出是CSV文件)。但是,PHP似乎没有释放用于字符串的旧副本的内存,因此有效地破坏了RAM与未使用的数据。切换到基于数组的方法(并在将其输出到outfile之前用逗号进行插入)可以避免这种行为。

出于某种原因 - 对我来说不明显 - PHP报告了json_decode调用期间内存使用量的增加,这误导了我认为json_decode函数是问题的假设。

答案 3 :(得分:9)

我发现PHP的内部内存管理器最有可能在完成一个函数时被调用。知道了,我已经在循环中重构了代码:

while (condition) {
  // do
  // cool
  // stuff
}

while (condition) {
  do_cool_stuff();
}

function do_cool_stuff() {
  // do
  // cool
  // stuff
}

修改

我运行了这个快速基准测试,没有看到内存使用量的增加。这让我相信泄漏不在json_decode()

for($x=0;$x<10000000;$x++)
{
  do_something_cool();
}

function do_something_cool() {
  $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
  $result = json_decode($json);
  echo memory_get_peak_usage() . PHP_EOL;
}

答案 4 :(得分:6)

在每个语句后调用memory_get_peak_usage(),并确保unset()尽一切可能。如果您使用foreach()进行迭代,请使用引用的变量来避免复制原始文件(foreach())。

foreach( $x as &$y)

如果PHP实际上泄漏了内存,强制垃圾收集将没有任何区别。

IBM

上有一篇关于PHP内存泄漏及其检测的文章

答案 5 :(得分:6)

我遇到了同样的问题并找到了可能的解决方法。

情况:我是从db查询写入csv文件的。我总是分配一个$行,然后在下一步重新分配它。取消$ row没有帮助;首先将5MB字符串放入$ row(以避免碎片)没有帮助;创建一个$ row-s数组(在其中加载许多行+在每5000步中取消整个行程)并没有帮助;真的尝试了几件事。

BUT。

当我创建一个打开文件的单独函数时,传输100.000行(刚好不要占用整个内存)并关闭文件,然后我对此函数进行了后续调用(附加到现有文件),I发现对于每个函数出口,PHP都删除了垃圾。这是一个局部可变空间的东西。

<强>结论: 每当函数退出时,它都会释放所有局部变量。

据我所知,这是规则。然而,只有一方需注意:当我试图让我的“do_only_a_smaller_subset()”函数通过引用获取一些变量(即查询对象和文件指针)时,垃圾收集没有发生。现在也许我误解了一些东西,也许查询对象(mysqli)漏了,好吧,我不知道。但是,由于它是由ref传递的,显然它无法清理,因为它存在于小函数的出口点之外。

所以,值得一试! 它节省了我的一天来找到它。

答案 6 :(得分:4)

我打算说我不一定会期望gc_collect_cycles()来解决问题 - 因为可能文件不再映射到zvars。但是你在加载任何文件之前是否检查过gc_enable被调用了?

我注意到PHP在执行包含时似乎吞噬了内存 - 远远超过源和标记化文件所需的内容 - 这可能是一个类似的问题。我不是说这是一个错误。

我相信一个解决方法是不使用file_get_contents而是使用fopen().... fgets()... fclose()而不是一次性将整个文件映射到内存中。但你需要尝试确认。

HTH

下进行。

答案 7 :(得分:3)

最近有similar issue System_Daemon。今天我将问题分离到了file_get_contents

您可以尝试使用fread吗?我认为这可以解决你的问题。 如果确实如此,那么可能是时候在PHP上进行错误报告了。