强制释放PHP中的内存

DBa*_*DBa 53 php garbage-collection

在PHP程序中,我按顺序读取一堆文件(带file_get_contents),gzdecode它们,json_decode结果,分析内容,抛出大部分文件,并在数组中存储大约1%.

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

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

小智 38

它与内存碎片有关.

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

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

''.join(['str1','str2','str3'])
Run Code Online (Sandbox Code Playgroud)

在python中

implode('', array('str1', 'str2', 'str3'))
Run Code Online (Sandbox Code Playgroud)

用PHP

sprintf等价物也没关系.

memory_get_peak_usage报告的内存基本上总是它必须使用的虚拟映射中的"最后"内存位.因此,由于它一直在增长,它报告了快速增长.因为每个分配落在当前使用的存储器块的"末尾".

  • 非常好的解释,+ 1. (2认同)

Mo.*_*Mo. 19

在PHP> = 5.3.0中,您可以调用gc_collect_cycles()强制GC传递.

注意:您需要在zend.enable_gc启用时php.ini启用,或者调用gc_enable()以激活循环引用收集器.

  • 试过 - 没效果. (8认同)
  • PHP 5.3+中的垃圾收集器不是*主要的内存管理机制.它专门用于处理循环引用的问题,主要基于refcount的系统无法处理.所描述的场景不涉及这样的循环引用,因此完全不受GC的影响. (6认同)
  • 我想你需要先调用gc_enable(). (4认同)

DBa*_*DBa 13

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

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

  • 你介意给出一些更详细的信息吗?它可能会帮助我.你在循环的每次迭代中重置了一个现有的字符串变量,但用于保存旧字符串的内存没有被释放 - 问题是什么?现在,使用数组来保存数据它会释放内存吗? (2认同)
  • 斯科特,很抱歉没有回复你:我正在覆盖已经使用过的字符串($ s = $ s."新内容";).虽然众所周知这种连接会调用新的分配,但我不知道旧的副本仍然存在并阻止内存.所以我切换到$ a = array(); array_push($ a,"new contents";)然后破坏数组. (2认同)

Mik*_*e B 9

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

while (condition) {
  // do
  // cool
  // stuff
}
Run Code Online (Sandbox Code Playgroud)

while (condition) {
  do_cool_stuff();
}

function do_cool_stuff() {
  // do
  // cool
  // stuff
}
Run Code Online (Sandbox Code Playgroud)

编辑

我运行这个快速基准测试并没有看到内存使用量的增加.这让我相信泄漏不在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;
}
Run Code Online (Sandbox Code Playgroud)

  • 在函数中包装东西与“调用 GC”无关。发生的情况是,在函数结束时,该函数中使用的所有变量同时超出作用域,就好像您同时使用了“unset()”一样。没有垃圾收集是这样完成的,变量只是达到 0 的引用计数并立即被释放。 (2认同)

And*_*ndy 6

memory_get_peak_usage()在每个声明后打电话,并确保unset()你能做到的一切.如果要迭代foreach(),请使用引用的变量以避免复制原始(foreach()).

foreach( $x as &$y)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 使用unset()是一个很好的解决方案,但你仍然依赖于GC.您也可以尝试将不再需要的变量分配给NULL.可以更快地回收存储器. (3认同)

sym*_*ean 6

我想说我不一定希望 gc_collect_cycles() 解决这个问题——因为大概这些文件不再映射到 zvar。但是在加载任何文件之前,您是否检查过 gc_enable 是否被调用?

我注意到 PHP 在执行包含时似乎会占用内存 - 比源文件和标记化文件所需的要多得多 - 这可能是一个类似的问题。我并不是说这是一个错误。

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

HTH

C。


dke*_*ner 6

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

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

但.

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

结论: 每当你的函数退出时,它就会释放所有局部变量.

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

所以,值得一试!它节省了我的一天,找到了这一点.