最近在写某个脚本时,在循环内反复调用了某个办法。依照以前的了解,办法在执行实现后,局部变量就生效了,它申请内存就开释了,但实际上并非如此。
<code class="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次,就会执行一次垃圾回收,也就是根缓冲区在第一万次的时候被填满了。
那么问题就来了,如果我的单个变量占内存比拟大,那么根缓冲区还没填满,就有可能把内存用完了,也就来不及从新分配内存,这就是可能导致内存透露的起因之一。比方上面这个例子
<code class="php"><?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个,还没来得及执行垃圾回收内存就被撑爆了。解决办法有两种
- 财大气粗的,间接加大分配内存🐶。然而这种办法个别用于应急应用,因为呈现内存泄露,根本代表程序多多少少有些问题,最好是找到内存应用过多的起因。
- 在适当的时候调用gc_collect_cycles()被动进行垃圾回收,开释多余的空间。
所以下面的代码最好的解决办法就是,隔一段时间就进行一次手动的垃圾回收。这样程序就能顺利跑完了。
<code class="php"><?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()。