【问题标题】:How to go about fixing a memory leak in PHP如何修复 PHP 中的内存泄漏
【发布时间】:2010-11-03 20:50:58
【问题描述】:

我的 PHP 应用程序有一个可以导入记录的导入脚本。

目前,它正在从 CSV 文件导入。它正在读取 CSV 文件的每一行,使用 fgetcsv 一次一行,并且对于每一行,它对该记录进行大量处理,包括数据库查询,然后继续下一行。它应该不需要继续积累更多的内存。

在导入大约 2500 条记录后,PHP 死了,说它已超出其内存限制(132 MB 左右)。

CSV 文件本身只有几兆 - 发生的其他处理会进行大量字符串比较、差异等。我有大量代码在上面运行,很难想出一个'最小的复制样本'。

有哪些好方法可以找到并解决此类问题?

找到问题的原因

我有一个调试类,它在运行时记录我的所有数据库查询。因此,那些大约 30KB 长的 SQL 字符串保留在内存中。我意识到这不适合设计为长时间运行的脚本。

可能还有其他内存泄漏源,但我相当确定这是我的问题的原因。

【问题讨论】:

  • +1 我在 PHP 中经常使用 CSV 文件,但最终会遇到这个问题。
  • 我在 CodeIgniter 处理大日志文件并插入数据库时​​遇到了类似的问题。我将插入更改为使用 CodeIgniters simple_query 而不是通常的 query 方法,并将内存使用量减少了 10 倍。

标签: php memory-leaks reference-counting


【解决方案1】:

如果您确实怀疑脚本中只有一两个内存泄漏导致它崩溃,那么您应该采取以下步骤:

  • memory_limit 更改为较小的值,例如 500KB
  • 注释掉除应用于每一行的一个处理步骤之外的所有处理步骤。
  • 对整个 CSV 文件运行有限的处理,看看它是否可以完成。
  • 逐渐添加更多步骤,并观察内存使用量是否达到峰值。

例子:

ini_set('memory_limit', 1024 * 500);
$fp = fopen("test.csv", 'r');
while($row = fgetcsv($fp)) {
    validate_row($row);         // step 1: validate
    // add these back in one by one and keep an eye on memory usage
    //calculate_fizz($row);     // step 2: fizz
    //calculate_buzz($row);     // step 3: buzz
    //triangulate($row);        // step 4: triangulate
}
echo "Memory used: ", memory_get_peak_usage(), "\n";

最坏的情况是所有您的处理步骤效率较低,您需要优化所有步骤。

【讨论】:

  • 感谢您的建议。对于有类似问题的任何人来说,这是一个很好的建议,但我只是碰巧发现 xdebug 的“跟踪”功能对于在这里查找原因最有用。
【解决方案2】:

查看代码会有所帮助,但如果您想自己调试,请查看Xdebug,它将有助于分析您的应用程序。

当然,根据您的操作,它可能会累积一些内存,尽管 132MB 对于 2500 条记录来说似乎已经很高了。当然,如果需要,你可以在 php.ini 中tweak your memory limit

您正在阅读的 CSV 文件有多大?你对它做了什么对象和什么样的处理?

【讨论】:

  • 感谢 xdebug 的建议。我现在找到了原因:)
【解决方案3】:

这取决于您在完成变量后如何清除它们。

您似乎已完成记录,但您仍在某处存储信息。如果有疑问,请使用unset() 清除变量。

请提供一个最小的重现代码示例,以查看如果这没有帮助,所有内存都去了哪里。

顺便说一句,生成能够重现问题的最小代码示例是一种很好的调试技术,因为它会迫使您再次仔细检查代码。

【讨论】:

    【解决方案4】:

    您可以尝试本地安装 php5.3 并调用 http://www.php.net/manual/en/function.gc-collect-cycles.php。

    gc_collect_cycles — 强制收集任何现有的垃圾循环

    如果情况有所改善,您至少验证了(一个或多个)问题。

    【讨论】:

      【解决方案5】:

      你是如何阅读文件的?如果您使用 fread/filegetcontents 或其他此类函数,那么您将在内存中消耗整个文件大小(或使用 fread 加载多少),因为整个文件在调用时加载。但是,如果您使用 fgetcsv if 一次只能读取一行,具体取决于行的长度,这会大大降低您的记忆力。

      还要确保在每个循环中重复使用尽可能多的变量。检查其中没有包含大量数据的数组。

      最后,请确保您在循环之前打开文件,然后在之后关闭它:

      $fh = fopen(...);
      while(true)
      {
      //...
      }
      fclose($fh);
      

      你真的不想这样做:

      while(true)
      {
      $fh = fopen(...);
      //...
      fclose($fh);
      }
      

      正如其他人所说,如果不看一些代码就很难判断。

      【讨论】:

      • 我使用的是 fgetcsv,抱歉我忘了提。我相当确定问题不在于读取文件,因为文件本身相对较小。
      【解决方案6】:

      不看代码很难说出原因。但是,一个典型的问题是递归引用,即。对象 A 指向对象 B,反之亦然,这可能会导致 GC 搞砸。

      我不知道您当前是如何处理文件的,但您可以尝试一次只读取一行文件。如果您一次读取整个文件,可能会消耗更多内存。

      这实际上是我经常更喜欢 Python 来处理批处理任务的原因之一。

      【讨论】:

      • 一次读取 CSV 一行。整个 CSV 只有几个兆;似乎是其他处理问题。
      • 不是叫循环引用吗?
      • @Schnalle 绝对正确。我太困了,记不住正确的术语 =)
      • PHP 5.3 中的垃圾收集器应该处理循环引用。在旧的 PHP 版本中,这确实是一个问题。
      【解决方案7】:

      你能在你的 php.ini 中改变你的 memory_limit 吗?

      另外,对变量执行 unset($var) 可以释放一些内存吗? $var = null 也有帮助吗?

      另请参阅此问题:What's better at freeing memory with PHP: unset() or $var = null

      【讨论】:

      • 好建议,但内存限制已经在 128MB 左右,增加它只会购买它运行时间更长的能力 - 最终我希望能够导入超过 10 个,也许是这个大小的 50 倍。
      • 是的,我意识到这不是理想的解决方案。只是想我会提到它。
      【解决方案8】:

      我遇到了同样的问题,也是由于数据库分析 (Zend_Db_Profiler_Firebug)。就我而言,它每分钟泄漏 1mb。这个脚本应该运行几天,所以它会在几个小时内崩溃。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-01-12
        • 2018-05-09
        • 2018-07-12
        • 2012-09-14
        • 2020-05-24
        相关资源
        最近更新 更多