为什么这个简单的PHP脚本会泄漏内存?

时间:2009-07-17 21:27:09

标签: php memory

为了避免在php程序(drupal模块等)中避免将来的内存泄漏,我一直在搞乱泄漏内存的简单php脚本。

一位php专家可以帮我找一下这个脚本会导致内存使用量持续攀升吗?

尝试自己运行,更改各种参数。结果很有趣。这是:

<?php

function memstat() {
  print "current memory usage: ". memory_get_usage() . "\n";
}

function waste_lots_of_memory($iters) {
  $i = 0;
  $object = new StdClass;
  for (;$i < $iters; $i++) {
    $object->{"member_" . $i} = array("blah blah blha" => 12345);
    $object->{"membersonly_" . $i} = new StdClass;
    $object->{"onlymember"} = array("blah blah blha" => 12345);
  }
  unset($object);
}

function waste_a_little_less_memory($iters) {
  $i = 0;
  $object = new StdClass;
  for (;$i < $iters; $i++) {

    $object->{"member_" . $i} = array("blah blah blha" => 12345);
    $object->{"membersonly_" . $i} = new StdClass;
    $object->{"onlymember"} = array("blah blah blha" => 12345);

    unset($object->{"membersonly_". $i});
    unset($object->{"member_" . $i});
    unset($object->{"onlymember"});

  }
  unset($object);
}

memstat();

waste_a_little_less_memory(1000000);

memstat();

waste_lots_of_memory(10000);

memstat();

对我来说,输出是:

current memory usage: 73308
current memory usage: 74996
current memory usage: 506676

[编辑以取消设置更多对象成员]

6 个答案:

答案 0 :(得分:34)

unset()不会释放变量使用的内存。当“垃圾收集器”(引用自PHP之前没有真正的垃圾回收器版本5.3.0,只是一个主要用于基元的无内存例程)看起来合适时,内存被释放。

另外,从技术上讲,您不需要致电unset(),因为$object变量仅限于您的功能范围。

这是一个演示差异的脚本。我修改了memstat()函数以显示自上次调用以来的内存差异。

<?php
function memdiff() {
    static $int = null;

    $current = memory_get_usage();

    if ($int === null) {
        $int = $current;
    } else {
        print ($current - $int) . "\n";
        $int = $current;
    }
}

function object_no_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {
        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);
    }
}

function object_parent_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {
        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);
    }

    unset ($object);
}

function object_item_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {

        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);

        unset ($object->{"membersonly_" . $i});
        unset ($object->{"member_" . $i});
        unset ($object->{"onlymember"});
    }
    unset ($object);
}

function array_no_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);
    }
}

function array_parent_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);
    }
    unset ($object);
}

function array_item_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);

        unset ($object["membersonly_" . $i]);
        unset ($object["member_" . $i]);
        unset ($object["onlymember"]);
    }
    unset ($object);
}

$iterations = 100000;

memdiff(); // Get initial memory usage

object_item_unset ($iterations);
memdiff();

object_parent_unset ($iterations);
memdiff();

object_no_unset ($iterations);
memdiff();

array_item_unset ($iterations);
memdiff();

array_parent_unset ($iterations);
memdiff();

array_no_unset ($iterations);
memdiff();
?>

如果您正在使用对象,请确保类实现__unset()以允许unset()正确清除资源。尽可能避免使用stdClass之类的变量结构类,或者将值分配给不在类模板中的成员,因为分配给它们的内存通常无法正确清除。

PHP 5.3.0及更高版本具有更好的垃圾收集器,但默认情况下禁用它。要启用它,您必须拨打gc_enable()一次。

答案 1 :(得分:23)

memory_get_usage()返回当前分配给PHP脚本的内存量(以字节为单位)。

这是操作系统分配给进程的内存量,分配的变量使用的内存量。 PHP并不总是将内存释放回操作系统 - 但是在分配新变量时仍然可以重用该内存。

证明这很简单。将脚本的结尾更改为:

memstat();
waste_lots_of_memory(10000);
memstat();
waste_lots_of_memory(10000);
memstat();

现在,如果你是正确的,PHP实际上是在泄漏内存,你应该会看到内存使用量增长两倍。但是,这是实际结果:

current memory usage: 88272
current memory usage: 955792
current memory usage: 955808

这是因为第二次调用会重新使用waste_lots_of_memory()的初始调用后“释放”内存。

在我使用PHP的5年中,我编写了在几个小时内处理了数百万个对象和千兆字节数据的脚本,以及一次运行数月的脚本。 PHP的内存管理并不是很好,但它并不像你想要的那么糟糕。

答案 2 :(得分:3)

memory_get_usage报告了从os分配了多少内存。它不一定与使用中的所有变量的大小匹配。如果php具有峰值内存使用,它可能决定不立即返回未使用的内存量。在您的示例中,函数waste_a_little_less_memory会随着时间的推移取消设置未使用的变量。因此峰值使用量相对较小。在释放它之前,waste_lots_of_memory构建了许多变量(=大量已用内存)。因此峰值使用量要大得多。

答案 3 :(得分:2)

我对memory_get_usage()的理解是它的输出可能取决于各种操作系统和版本因素。

更重要的是,取消设置变量不会立即释放它的内存,将其从进程中释放出来,并将其返回给操作系统(同样,此操作的特性依赖于操作系统)。

简而言之,您可能需要更复杂的设置来查看内存泄漏。

答案 4 :(得分:0)

我不确定它在PHP中的确切运作方式,但在其他一些语言中,包含其他对象的对象在设置为null时并不会将其他对象固有地设置为null。它终止了对这些对象的引用,但由于PHP在Java意义上没有“垃圾收集”,所以子对象存在于内存中,直到它们被单独删除。

答案 5 :(得分:0)

memory_get_usage()不返回立即内存使用情况,而是存储内存以运行该进程。 在一个庞大的数组unset($ array_a)的情况下,不会释放内存,但根据我系统中的memory_get_usage()消耗更多...

$array_a="(SOME big array)";
$array_b="";
//copy array_a to array_b
for($line=0; $line<100;$line++){
$array_b[$line]=$array_a[$line];
}

unset($array_a); //having this shows actually a bigger consume
print_r($array_b);

echo memory_get_usage();