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

2022-08-30 23:56:33

希望避免将来在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

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


答案 1

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()一次。


答案 2

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的内存管理不是很好,但它并不像你想象的那么糟糕。


推荐