乐趣区

关于php:php内存泄露的原因分析和垃圾回收机制的探讨

最近在写某个脚本时,在循环内反复调用了某个办法。依照以前的了解,办法在执行实现后,局部变量就生效了,它申请内存就开释了,但实际上并非如此。

<?php
class Foo
{public $var = '3.1415962654';}

$baseMemory = memory_get_usage();

for ($i = 0; $i <= 100000; $i++)
{f($i, $baseMemory);
}

function f($i, $baseMemory)
{
    $a = new Foo;
    $a->self = $a;
    if ($i % 500 === 0)
    {echo sprintf( '%8d:', $i), memory_get_usage() - $baseMemory, "\n";}
}

运行下面这段代码后发现,php 的内存并不是来到函数就开释,而是达到肯定值后才会进行开释(只探讨 php5.3 之后的机制)。官网的说法是

首先,实现垃圾回收机制的整个起因是为了,一旦先决条件满足,通过清理循环援用的变量来节俭内存占用。在 PHP 执行中,一旦根缓冲区满了或者调用 gc_collect_cycles() 函数时,就会执行垃圾回收。

也就是说只有根缓冲区满了,php 就会执行垃圾回收,开释那些没用到的内存。

那么什么是“根缓冲区”呢?根缓冲区就是拿来寄存所有可能根(能够了解为 php 里的变量)的容器,他的值是 10000,能够批改 PHP 源码文件 Zend/zend_gc.c 中的常量 GC_ROOT_BUFFER_MAX_ENTRIES,而后从新编译 PHP,来批改这个 10000 值。

如果没有批改过根缓冲区的值,察看下面的代码就会发现,每 10000 次,就会执行一次垃圾回收,也就是根缓冲区在第一万次的时候被填满了。

那么问题就来了,如果我的单个变量占内存比拟大,那么根缓冲区还没填满,就有可能把内存用完了,也就来不及从新分配内存,这就是可能导致内存透露的起因之一。比方上面这个例子

<?php
ini_set('memory_limit', '128M');

class Use10MClass
{
    public $var = null;

    public function __construct()
    {$this->var = str_pad('1', 10 * 1024 * 1024);

    }
}

$baseMemory = memory_get_usage();
echo "以后内存:", memory_get_usage(), "\n";

for ($i = 0; $i <= 100; $i++) {test($i, $baseMemory);
}

function test($i, $baseMemory)
{$b = new Use10MClass();
    $b->self = $b;
    echo sprintf('%8d:', $i), memory_get_usage() - $baseMemory, "\n";}

在第十一次循环时,就报了 PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to all
ocate 10485785 bytes) 这个谬误,因为单个变量所耗费的内存过多,根缓冲区才被填了 11 个,还没来得及执行垃圾回收内存就被撑爆了。解决办法有两种

  1. 财大气粗的,间接加大分配内存🐶。然而这种办法个别用于应急应用,因为呈现内存泄露,根本代表程序多多少少有些问题,最好是找到内存应用过多的起因。
  2. 在适当的时候调用 gc_collect_cycles() 被动进行垃圾回收,开释多余的空间。

所以下面的代码最好的解决办法就是,隔一段时间就进行一次手动的垃圾回收。这样程序就能顺利跑完了。

<?php
ini_set('memory_limit', '128M');

class Use10MClass
{
    public $var = null;

    public function __construct()
    {$this->var = str_pad('1', 10 * 1024 * 1024);

    }
}

$baseMemory = memory_get_usage();
echo "以后内存:", memory_get_usage(), "\n";

for ($i = 0; $i <= 100; $i++) {test($i, $baseMemory);
    // 每八次进行一下垃圾回收
    if ($i % 8 === 0) {gc_collect_cycles();
    }
}

function test($i, $baseMemory)
{$b = new Use10MClass();
    $b->self = $b;
    echo sprintf('%8d:', $i), memory_get_usage() - $baseMemory, "\n";}

另外,unset 变量并不会立刻开释内存,该溢出的时候还是会溢出的。在函数最初一句 unset 局部变量是没有意义的。

总的来说就是变量尽管无奈应用了,然而他所占用的内存空间并没有被占用。个别的程序期待根缓冲区满了,主动垃圾回收就能够了。然而对一些变量比拟大的状况,能够在适当的时候执行 gc_collect_cycles() 被动进行垃圾回收,防止内存泄露。程序进行垃圾回收是会耗费肯定的工夫的,所以也不举荐频繁调用 gc_collect_cycles()。

退出移动版