最近有人跟我反映,主机上一个 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 本身这个内存限制功能有 Bug,于是做了如下实验:
- 生成一个 10MB 大小的文件:
dd if=/dev/zero of=10mb bs=1M count=10
- 生成如下 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 内存限制调得很大,虽然可能暂时解决问题,但并不一定是最好的办法。更合理的方式还是先分析程序为什么会消耗这么多内存。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。