为了避免在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
[编辑以取消设置更多对象成员]
答案 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();