PHP进程的内存使用情况

13

总结


简要建议(更详细的信息请参见答案)

为避免内存泄漏,您可以:

  1. 一旦变量变得无用,就立即取消设置它们
  2. 您可以使用xdebug获取函数的内存消耗详细报告并查找内存泄漏
  3. 您可以设置memory_limit(例如为5Mb),以避免虚拟内存分配

问题

除库和变量之外,PHP可以使用内存来做什么?我通过这段代码监视变量使用的内存,结果大约为3Mb:

$vars = array_keys(get_defined_vars());
        $cnt_vars = count($vars);
        $allsize = 0;
        for ($j = 0; $j < $cnt_vars; $j++) {

            try
            {
                $size = @serialize($$vars[$j]);
                $size = strlen($size);
            }
            catch(Exception $e){
                $str = json_encode($$vars[$j]);
                $str = str_replace(array('{"','"}','":"','":'), '', $str);
                $size = strlen($str);
            }
            $vars[$j] = array(
                'size' => $size,
                'name' => $vars[$j]
            );
            $allsize += $size;
        }

库文件(例如libcurl等)占用大约18MB的空间。因此总共是21MB,但是

pmap -x (进程名) 显示总内存占用量为kB:314028 RSS:74704 Dirty:59672

因此,实际内存消耗总共约为74MB。 我还看到一些在pmap中具有[anon]映射的大块。PHP用这些块做什么?

PHP版本:5.5.9-1ubuntu4.14 PHP扩展:

root@webdep:~# php -m
[PHP Modules]
bcmath
bz2
calendar
Core
ctype
curl
date
dba
dom
ereg
exif
fileinfo
filter
ftp
gd
gettext
hash
iconv
json
libxml
mbstring
mcrypt
mhash
openssl
pcntl
pcre
PDO
pdo_pgsql
pgsql
Phar
posix
readline
Reflection
session
shmop
SimpleXML
soap
sockets
SPL
standard
sysvmsg
sysvsem
sysvshm
tokenizer
wddx
xml
xmlreader
xmlwriter
Zend OPcache
zip
zlib

[Zend Modules]
Zend OPcache

@Smar PHP版本 => 5.5.9-1ubuntu4.14 - fiction
2
你还应该考虑使用Xdebug进行分析,它可以给你函数调用级别的内存使用增量,并且可以生成callgrind文件,你可以使用像kcachegrind这样有用的工具来分析。 - Smar
1
你是通过Apache模块还是fastcgi/fpm或cli/cgi版本来执行这个程序的?不同的版本会以不同的方式分配和释放内存。请记住,有些内存被分配但未被使用。如果无法使用cachegrinder进行分析,请尝试使用memory_get_usage()memory_get_usage(true)进行测试。 - Daniel W.
@fiction:我的评论有点长了,所以我添加了一个答案。 - Smar
1
@Smar 我花了一些时间研究了 PHP 5.5、5.6 和 7 的内存利用情况。结果发现 PHP7 在 CPU 使用方面有所改进,但内存利用仍然相当。如果你担心内存使用过高,可以尝试使用 HHVM。 - Uday Sawant
显示剩余11条评论
4个回答

6
PHP不同于编译成单个二进制代码的C或CPP代码。所有脚本都在Zend虚拟机内执行。大部分内存由VM本身消耗,包括已加载扩展、PHP进程使用的共享库(.so文件)和任何其他共享资源所使用的内存。

我记不清确切的来源了,但我在某处读到,近70%的总CPU周期被PHP内部消耗,只有30%到达您的代码(如果我在这里错了,请纠正我)。这与内存消耗没有直接关系,但应该可以让您了解PHP的工作原理。

关于anon块,我在另一个SO答案中找到了一些细节。答案是关于Java的,但同样适用于PHP。

Anon块是通过malloc或mmap分配的“大”块-请参阅manpages。因此,它们与Java堆没有任何关系(除了整个堆应该存储在这样的块中)。

我建议禁用一些扩展程序。这应该可以节省一些未使用的内存。


所以一个有趣的尝试是将内存限制降低到5 MB左右;如果这能够解决OP案例中过度的内存分配问题,那么我猜这只是PHP的虚拟分配逻辑(就像在Java的情况下一样,他们已经尝试在最新的Java版本中修复了这个问题AFAIK :-)。 - Smar
1
@Smar 现在检查一下,看起来是正确的,但我需要测试一两天,我会回复。 - fiction

5

注意:这不是一个确切的答案,而是由OP请求的信息,但评论字段对此太短了... 这些更多是调试此类问题的工具。

Xdebug的文档非常全面, 它们应该比我复制到这里更好地告诉您如何使用它。您提供的脚本有点模糊,因此我没有自己进行跟踪,但它会为您提供逐行内存使用差异。

基本上,启用Xdebug并将xdebug.show_mem_delta设置为1以生成函数跟踪,然后您可以在文本编辑器中打开它,以查看哪个部分泄漏内存。

然后,您可以比较初始(或中间位置)总内存,以查看它与您正在看到的实际内存使用情况相差多少。

TRACE START [2007-05-06 14:37:26]
    0.0003     114112  +114112   -> {main}() ../trace.php:0

这里的总内存将是114112

如果差异非常大,您可能想要使用类似shell_exec()的东西,在所有行之间获取实际的内存使用情况,并输出它,然后您可以将该输出与Xdebug的内存输出进行比较,以查看差异发生的位置。

如果差异来自脚本的第一行,罪魁祸首可能是PHP的扩展。查看php -m是否存在任何可疑的扩展。


我该如何检查可疑的扩展?我在问题中添加了一些PHP扩展列表。 - fiction
1
你可以将它们全部禁用,然后逐个启用以进行测试。 - quickshiftin
1
我想到的一件事是OPCache,因为它是Zend的新加速技术,所以它可以节省一些意想不到的东西。但是@quickshiftin说的也有道理。 - Smar
@Smar 调试了几天,但我仍有一个问题...在跟踪的末尾,xdebug显示内存中有24735064字节(约23.9 Mb)但是在pmap中,我可以看到进程使用了76 Mb(堆使用了60Mb)。所以,我无法想象这30 Mb用于什么:(并且我发现,进程一开始使用大约23 Mb,然后突然使用了76Mb。 - fiction
@fiction 在函数跟踪中,您可以找到内存使用量增加的位置,以便了解代码中发生增加的地方?然后,在该点之后放置pmap调用(shell_exec("..pmap..")),将它们重定向到文件中,以查看内存是否会减少,如果PHP内存的增加发生在同一位置,则可以确定问题所在。如果增加后从未减少,则很可能与内存分配算法有关,就像Uday在答案中所解释的那样。 - Smar
1
更多信息,内存分配器(至少在Java中,但我认为在PHP中也是如此;我怀疑PHP通常会以不良方式泄漏内存...)有时会预先分配它需要的更大的内存块,这样它就不需要一直进行分配。而Java过去从未释放该内存,出于同样的原因。 - Smar

2

首先创建一个数组,以便调查其所占用的内存

$startMemory = memory_get_usage();
$array = range(1, 100000);
echo memory_get_usage() - $startMemory, ' bytes';

一个整数在 64位unix机器 上使用 long 类型占用 8字节,这里有 100000个整数,因此你显然需要 800000字节。这大约是 0.76 MB

这个数组占用了 14649024字节。那就是 13.97 MB - 比预估的多了十八倍

下面是涉及不同组件内存使用情况的快速总结:

                             |  64位   | 32位
---------------------------------------------------
zval                         |  24字节 | 16字节
+ 循环GC信息                 |   8字节 |  4字节
+ 分配标头                   |  16字节 |  8字节
===================================================
zval(值)总计               |  48字节 | 28字节
===================================================
bucket                       |  72字节 | 36字节
+ 分配标头                   |  16字节 |  8字节
+ 指针                       |   8字节 |  4字节
===================================================
bucket(数组元素)总计       |  96字节 | 48字节
===================================================
总计总计                     | 144字节 | 76字节

对于大型静态数组,如果我调用如下方式:

$startMemory = memory_get_usage();
$array = new SplFixedArray(100000);
for ($i = 0; $i < 100000; ++$i) {
$array[$i] = $i;
}
echo memory_get_usage() - $startMemory, ' bytes';

这将导致结果为5600640字节

每个元素只有56字节,远小于普通数组使用的144字节。这是因为固定数组不需要桶结构,所以每个元素只需要一个zval(48字节)和一个指针(8字节),从而得到观察到的56字节

希望这对你有所帮助。


0

你看到的数字没有问题,不应该将它们合并,这只是“三倍化”,你看到的是库的不同部分(只读、可执行、可写)分别列出,你的数字是正确的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接