【问题标题】:PHP: Having some memory issues inside a loopPHP:在循环中存在一些内存问题
【发布时间】:2015-08-06 18:57:57
【问题描述】:

我有一个类似于以下内容的 sn-p:

while (true) {
    $myObject = new Class();
    $myOtherObject = $myObject->getSomeOtherObj();
    ...
    $myArray = [1, 2, 3];
    ...
    sleep(1); //Sleep at the end, to save CPU.
}

这个 sn-p 应该作为守护程序服务运行,但我在完成这项工作时遇到了很多麻烦。

问题:每次迭代都会增加进程内存使用量。好像在每次新的迭代中都会实例化一个新的$myObject,但前一个仍然分配在内存中,等等。

我试过了:

  • unset循环结束时的所有变量(就在sleep()之前)。
  • 将所有变量设置为null
  • 将它们封装在一个单独的函数中 (while (true) { doThis(); })
  • 手动拨打gc_collect_cycles()

这些都不能减少内存使用。

我不知道如何强制释放所有内存。

【问题讨论】:

  • 你到底要做什么,这好像是死循环了!
  • 要避免无限循环
  • 这是一个无限循环,因为这是一个守护服务。这将监听一些变化并且永远不应该退出。我真的需要解释守护服务的概念吗?
  • @zairwolf 你所说的基本上是“释放内存,退出程序然后重新启动它”。这是一个非答案,一个非常糟糕的解决方法,在我的规范中是不可能的。
  • 你能用generator 处理循环吗?有用的tutorial

标签: php memory garbage-collection


【解决方案1】:

经过对该主题的大量研究,我终于确信没有办法手动强制释放内存或强制对象销毁。

然而,帮助我降低内存使用量(绝对防止无限内存堆叠是不可能的)的事情是意识到 PHP 中没有 没有循环范围,并且垃圾收集在切换时发生范围。

在 C# 或 Java 中,在 while (...) {} 中创建的变量只能从循环内访问。这不是 PHP 的标准。从 while 指令中创建的 $myObject 可在整个应用程序中访问。

这意味着提供的 sn-p 会更好地呈现为:

while (true) {
    myFunc();
}

function myFunc()
{
    $myObject = new Class();
    $myOtherObject = $myObject->getSomeOtherObj();
    ...
    $myArray = [1, 2, 3];
    ...
    sleep(1); //Sleep at the end, to save CPU.
}

将逻辑封装在函数中会强制改变作用域,这意味着垃圾收集器将在每次迭代时被调用。这没有解决了我的问题,但它在一定程度上降低了我的 RAM 使用率。

我从这次经历中学到的是,PHP 可能不适合这个特定的项目需求。我需要对内存进行更多控制,而 PHP 不提供对创建/销毁对象的任何控制。一些本机函数不能正确释放内存(特别是那些执行 I/O、数据库访问和 memcached 的函数)。

【讨论】:

  • 这样的东西对内存使用有帮助吗?函数 myFunc($constraint) { $myObject = new Class{}; $myOtherObject = $myObject->getSomeOtherObj(); $myArray = [1, 2, 3];睡眠(1); //最后休眠,节省CPU。 register_shutdown_function('myFunc', $constraint - 1);出口(); } myFunc(100);
【解决方案2】:

我在这里的答案中编译了我以前的 cmets。这并没有准确解释如何释放分配的内存,但它将引导您通过一种方法来发现应用程序中的原因。这样,您就可以优化您的代码了。

查找内存使用瓶颈通常是一项具有挑战性的任务。您可以从查看与 I/O 相关的调用开始,例如数据库查询、文件访问甚至网络。除了增加执行时间之外,有时这些操作还会分配一些内存。

如果您已经从内存中删除了由 I/O 操作返回的资源,并且没有注意到分配的内存明显减少,那么下一步可能是使用 Blackfire (https://blackfire.io/) 之类的工具来分析您的应用程序。

Blackfire 将为您提供每个函数调用的详细视图及其关于内存、CPU 和执行时间的统计信息。有了这些数据,就可以检查哪些操作分配了过多的内存。当您将鼠标指针放在通话详细信息内的内存条上时,您可以找到此信息,如下所示:

【讨论】:

  • 虽然这不是实际问题的答案,但此过程帮助我跟踪所有内存的去向,这使我意识到我的应用程序内核正在“开发”模式下运行,并带有很多调试器。我已经切换到“prod”模式,它禁用了所有调试器。这并没有解决内存堆叠问题,但减少了如此多的内存使用量,以至于我可以堆叠数亿个进程而不会耗尽内存。我现在每年只需要重新启动一次服务器,就我目前的使用而言。如果没有人发布实际答案,我会给你赏金。
【解决方案3】:

很有可能(提供给定的信息)仍然存在对已创建对象的引用,这会阻止垃圾收集器从内存中获取removing the object。由于它基本上是在计算引用,因此可以通过复制值或小心地取消设置来确保没有存储引用。

通常情况下,使用 while(true) 构造来避免精确地创建对象并使其尽可能自包含以确保实际上不会发生内存泄漏会更容易。

我知道这个答案在直接的方式上不是很有帮助(而且我没有足够的代表来评论这个问题)但它可能会让你走上正确的轨道。

【讨论】:

  • It is very likely [...] that there are still references to the created object which prevent the garbage collector from removing the object from memory. 这是我想到的第一件事,也是我搜索的第一件事。我检查了很多次,我不相信这是问题所在。当然,我可能是错的。
【解决方案4】:

问题是您处于无限循环中,请求没有尽头。 PHP 的垃圾收集器设计为在请求结束时处理,否则用户无法访问。 PHP 旨在被调用和丢弃,而不是无限期地保持活动状态。因此它是不可修复的。因此,我建议创建一个 chron 作业,定期重新启动 php 循环,从而结束请求并释放内存。详情请见this document

【讨论】:

    【解决方案5】:

    我最好的猜测(由于缺乏对所涉及类的内部结构的了解)是这些类将其他对象分配为它们的属性(或者它们可能具有自引用)或使用的数组(因为代码示例类似于真实情况)有一个对自身的引用,如果数组的大小很大,这将解释内存泄漏。

    如果有任何帮助,请查看 php.net 中的引用计数基础知识:

    http://php.net/manual/en/features.gc.refcounting-basics.php

    【讨论】:

      【解决方案6】:

      正如@m1lt0n 和@MartPluijmaekers 上面提到的,这可能是与对象引用有关的问题。

      我们不知道你的 Class() 类和 getSomeOtherObj() 方法里面有什么,所以我不能肯定地说什么,但是下面的 sn-p 可能可以帮助你弄清楚是否是这种情况与否。

      /* Here is your Class */
      class Class {
      
          public function __construct () {
              $this->child = new AnotherClass( $this );
          }
      
          /* This is what you have to have ... */
          protected function __destruct () {
              unset( $this->child );
          }
      }
      
      /* Here is the other class  */
      class AnotherClass {
      
          public function __construct ( $parent ) {
              $this->parent = $parent;
          }
      }
      
      /* Infinite Loop */
      while ( true ) {
      
          $myObject = new Class();
          $myOtherObject = $myObject->getSomeOtherObj();
      
          $myArray = [1, 2, 3];
      
          /* manually destroying the object */
          $myObject->__destruct();
          unset( $myObject );
      
          /* rest of free-ing goes here ... */
      
          sleep(1); //Sleep at the end, to save CPU.
      }
      

      sn-p 应该是不言自明的。

      【讨论】:

      • unset() 将自动在 $myObject 上调用 __destruct,只要没有更多引用。你不需要手动调用它,它只是噪音。
      • @JoshTriiJohnston 感谢您的评论。那很有意思。有参考吗?
      • “只要没有其他对特定对象的引用,就会调用析构函数”php.net/manual/en/… 不过,您必须在子类中调用父类的析构函数。
      • @JoshTriiJohnston 很高兴知道这一点。正如它所说,析构函数不会自动触发,所以我认为在这种特定情况下它不会让它变得多余——示例中的 Class 是父级。
      • parent 析构函数不会被引擎隐式调用”。虽然你可能需要手动调用 gc_collect_cycles() 来强制删除,如果它发生得不够快。
      猜你喜欢
      • 2013-12-25
      • 1970-01-01
      • 2015-11-27
      • 2015-11-18
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      • 2011-11-22
      • 2018-01-02
      相关资源
      最近更新 更多