【问题标题】:Memory leak in Magento/Zend FrameworkMagento/Zend 框架中的内存泄漏
【发布时间】:2013-04-13 15:43:13
【问题描述】:

运行这个简单的脚本时,我得到了下面发布的输出。 这让我觉得我的代码或 Zend Framework/Magento 堆栈中存在内存泄漏。迭代任何类型的 Magento 集合时会出现此问题。 我有什么遗漏或做错了吗?

脚本:

$customersCollection = Mage::getModel('customer/customer')->getCollection();

foreach($customersCollection as $customer) {
  $customer->load();
  $customer = null;
  echo memory_get_usage(). "\n";
}

输出:

102389104
102392920
...
110542528
110544744

【问题讨论】:

  • 这是我找到的另一个reference。看起来问题出在循环引用上。
  • @osondoar 如果您至少使用 PHP 5.3(您现在应该是)循环引用将被垃圾收集器捕获,尽管不是立即。但是,请参阅我的回答,了解为什么您的示例甚至不会释放非循环引用。
  • 鉴于 Magento 的 ORM,这不适合使用 load()。你想完成什么?
  • 是的,在 PHP 的 OOP 实现中可能会发生内存泄漏。请参阅此处了解这对 Magento 的影响:magentocommerce.com/blog/garbage-collectorclearInstance 方法(如果在对象中实现,$customer->clearInstance())有时可以清除这些引用,但并不总是可以控制特定方法调用中的所有内容。例如,您对 load 的调用缺少 ID,这意味着 Magento 尝试使用空白 ID 重新加载对象,这会导致各种奇怪的实例化和对象清理,从而触发泄漏行为。
  • @AlanStorm 再一次,该文章将 PHP 的内存管理器(当引用计数达到零时立即释放内存)与 PHP 5.3+ 的垃圾收集器(定期检查循环引用,即使它们不可访问,其引用计数也永远不会为零)。

标签: php magento zend-framework


【解决方案1】:

您的问题是每次迭代都会发出相当昂贵的查询,而您可以通过集合查询加载必要的数据:

$collection = Mage::getResourceModel('customer/customer_collection')->addAttributeToSelect('*');

会做同样的事情,但都在一个查询中。这种方法需要注意的是,如果有任何 customer_load_beforecustomer_load_after 事件的自定义事件观察器(这些事件没有核心观察器),则需要为每个数据模型手动运行观察器。

编辑:感谢 osonodoar 发现错误的类引用(客户/客户与客户/客户集合)

【讨论】:

    【解决方案2】:

    对象(或其他值)的内存只有在 PHP 进程中的任何地方都没有引用时才能被释放。在您的情况下,$customer = null 行只会将对该对象的引用数量减少一,但不会使其达到零。

    如果你考虑一个更简单的循环,这可能会变得更清楚:

    $test = array('a' => 'hello');
    foreach ( $test as $key => $value )
    {
        // $value points at the same memory location as $test['a']
        // internally, that "zval" has a "refcount" of 2
    
        $value = null;
        // $value now points to a new memory location, but $test['a'] is unnaffected
        // the refcount drops to 1, but no memory is freed
    }
    

    因为您使用的是对象,所以有一个额外的转折 - 您可以在循环内修改对象而无需创建它的新副本:

    $test = array('a' => new __stdClass);
    // $test['a'] is an empty object
    
    foreach ( $test as $key => $value )
    {
        // $value points at the same object as $test['a']
        // internally, that object has a "refcount" of 2
    
        $value->foo = "Some data that wasn't there before";
        // $value is still the same object as $test['a'], but that object now has extra data
        // This requires additional memory to store that object
    
        $value = null;
        // $value now points to a new memory location, but $test['a'] is unnaffected
        // the refcount drops to 1, but no memory is freed
    }
    
    // $test['a']->foo now contains the string assigned in the loop, consuming extra memory
    

    在您的情况下,->load() 方法可能会依次扩展$customersCollection 的每个成员中的数据量,每个成员都需要更多内存。在循环前后检查$customersCollection 可能会证实这一点。

    【讨论】:

      【解决方案3】:

      首先,取消设置变量时使用 unset($variable) 而不是 $variable=null。它基本上做同样的事情,但你的意图要清楚得多。

      其次,PHP 注定要死掉——内存泄漏不是一个大问题,因为 PHP 请求可能会持续几秒钟,然后进程就会死掉,并且它正在使用的所有内存都被释放以供下一个请求使用。除非您遇到扩展问题,否则无需担心。

      编辑:这并不是说不要担心代码的质量,但是对于这样的事情,除非它引起问题,否则很可能不值得努力阻止它发生。

      【讨论】:

      • 感谢 cmets。这里的主要问题是,当迭代超过 50000 个客户时,会出现内存分配问题。例如,如果您将内存限制设置为 512Mb,脚本将会崩溃
      【解决方案4】:

      处理内存泄漏的其他方法是在循环中调用 exec 并让该 exec 函数完成导致内存泄漏的工作部分。

      因此,一旦它完成了它的部分并终止了该 exec 中的所有内存泄漏。

      因此,随着大量迭代,这种不断增加的内存损失将得到处理。

      【讨论】:

        【解决方案5】:

        @benmarks 响应在这里是正确的方法,因为在循环中调用 load() 是一个非常昂贵的调用。

        调用 $customer->load() 将增量分配内存,该内存将被 $customersCollection 引用,直到循环结束时才会释放内存。

        但是,如果由于某种原因需要调用 load(),下面的代码不会泄漏内存,因为 GC 会在每次迭代中释放模型分配的所有内存。

        $customersCollection = Mage::getModel('customer/customer')->getCollection();
        
        foreach($customersCollection as $customer) {
            $customerCopy = Mage::getModel('customer/customer')->load($customer->getId());
        
            //Call to $customerCopy methods
        
            echo memory_get_usage(). "\n";
        }
        

        【讨论】:

        • 你的代码泄露给我了。添加$customerCopy->clearInstance(); 可以解决问题。
        • 嗯...当我执行时,它看起来好像有泄漏但是 GC 能够每 5 秒左右释放一次内存,将内存消耗保持在一定水平。不同的 PHP/Magento 配置可以解释为什么我们得到不同的结果。无论如何,你的回应是要走的路,谢谢!
        猜你喜欢
        • 2011-05-29
        • 2011-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-20
        • 2013-03-30
        • 1970-01-01
        相关资源
        最近更新 更多