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

mjg*_*ins 15 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();
Run Code Online (Sandbox Code Playgroud)

对我来说,输出是:

current memory usage: 73308
current memory usage: 74996
current memory usage: 506676
Run Code Online (Sandbox Code Playgroud)

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

And*_*ore 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();
?>
Run Code Online (Sandbox Code Playgroud)

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

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


Fra*_*mer 23

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

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

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

memstat();
waste_lots_of_memory(10000);
memstat();
waste_lots_of_memory(10000);
memstat();
Run Code Online (Sandbox Code Playgroud)

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

current memory usage: 88272
current memory usage: 955792
current memory usage: 955808
Run Code Online (Sandbox Code Playgroud)

这是因为在第二次调用时重新使用了waste_lots_of_memory()的初始调用后"释放"的内存.

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