数字营销 · Web开发 · 基础设施

PHP内存耗尽错误分析

分析 PHP 中 “Allowed memory size exhausted” 错误的原因,通过实际测试解释 memory_limit、内存占用和单次内存申请之间的关系,并介绍排查 PHP 内存问题的方法。

最近有人跟我反映,主机上一个 WordPress 插件在使用过程中经常会出现错误。打开 PHP 的 display_error 功能之后,发现报错:

Fatal error: Allowed memory size of 35389440 bytes exhausted
(tried to allocate 1406507 bytes) in xxxxxx on line xxx

意思是 PHP 运行过程中可用内存已经耗尽。

在 Google 上搜索了一下,网上有很多类似的问题,而答案基本也都一样:修改 PHP 的内存限制,把 php.ini 中的 memory_limit 设置得更大。

但是这里有一个很大的问题:PHP 允许访问的内存大小是 35389440 字节,而实际尝试分配的只有 1406507 字节。为什么要分配的内存比实际允许使用的内存小,还是会引发致命错误呢?

PHP内存耗尽错误

查了很多这方面的资料,发现很多类似的搜索结果中,实际要分配的内存确实小于允许使用的内存,但是对于这种情况并没有太多说明。

首先想到的是不是 PHP 本身这个内存限制功能有 Bug,于是做了如下实验:

  1. 生成一个 10MB 大小的文件:
dd if=/dev/zero of=10mb bs=1M count=10
  1. 生成如下 PHP 文件并访问:
<?php
ini_set("memory_limit", "2M");
echo file_get_contents("./10mb");
?>

发现报错:

Fatal error: Allowed memory size of 2097152 bytes exhausted
(tried to allocate 10493952 bytes)

这个报错和我们设置的 2M 内存限制、实际要分配 10M 内存的情况是对应的。

这说明 PHP 的内存限制本身没有问题,报错中的数据和实际申请的内存大小也是能够对应上的。

那么,上面的问题又出在哪里呢?为什么实际要分配的内存比限制的内存小,还会导致错误?

又查了一下 PHP 关于 memory_limit 的说明:

This sets the maximum amount of memory in bytes that a script is allowed to allocate. This helps prevent poorly written scripts for eating up all available memory on a server. Note that to have no memory limit, set this directive to -1.

这里同样没有直接解释这个问题,只是告诉我们 memory_limit 用来限制单个 PHP 脚本能够分配的内存,以避免程序消耗掉服务器上的全部内存。

那么,会不会存在这样一种情况:整个脚本运行过程中已经使用了大量内存,而当前这一次操作还需要申请一小部分内存,但剩余可用内存已经不足,所以最终申请失败?

为了验证这个猜想,我把上述 PHP 文件改成这样并访问:

<?php
ini_set("memory_limit", "15M");

$a = file_get_contents("./10mb");
$b = file_get_contents("./10mb");
?>

发现报错:

Fatal error: Allowed memory size of 15728640 bytes exhausted
(tried to allocate 10493952 bytes)

实际第二次操作需要分配约 10M 内存,小于设置的 15M 内存限制,但依然发生了错误。

这也就验证了前面的猜想。

实际上,memory_limit 限制的是整个 PHP 脚本运行过程中可以使用的内存总量,而不是某一次操作单独可以使用多少内存。

所以,当程序当前已经占用了大量内存时,即使下一次操作只需要申请较小的一块内存,也可能因为剩余可用内存不足而失败。

这也解释了为什么错误信息中会出现:

Allowed memory size of 35MB exhausted
tried to allocate 1.4MB

这两个数字并不是简单的大小比较关系。前一个是整个脚本的内存限制,后一个则是当前操作尝试申请的内存大小

那么直接增加内存限制的大小就可以解决这个问题了吗?

增加 memory_limit 的确可以缓解部分问题,但更重要的是找到真正消耗内存的代码。

可以通过 memory_get_usage()memory_get_peak_usage() 等函数统计脚本运行过程中的内存使用情况,根据实际情况找到内存消耗较大的代码,再决定是否需要调整 memory_limit

因此,遇到这类错误时,直接把 PHP 内存限制调得很大,虽然可能暂时解决问题,但并不一定是最好的办法。更合理的方式还是先分析程序为什么会消耗这么多内存。

参考资料

  1. memory limit

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。