一句话总结:
只要你的 PHP 程序用到的内存超过memory_limit,就会直接报 Fatal error,然后整个程序中断。
很多人第一次遇到这个报错:
Fatal error: Allowed memory size of xxx bytes exhausted
脑袋一片空白:
到底是 PHP 太弱?还是我代码写得太烂?还是服务器配置太差?
今天我用**最通俗、最「人话」**的方式,把 PHP 内存溢出的所有坑统一讲透。
一、PHP 内存溢出的本质:用超了你的“套餐”
假设:
- 你每个月只有 10G 流量(PHP 的 memory_limit)
- 你看视频花了 9.8G
- 你又打开了一个高清视频……
啪!流量超了,直接断网。
PHP 也是一样:
- 给 PHP 分配的内存就那么多
- 当你的代码需要的内存 > memory_limit
- PHP 就会直接中断,抛 Fatal error
memory_limit 在配置里长这样:
memory_limit = 128M
也可能是 256M、512M。
二、内存溢出最常见的 10 类原因(80% 的开发者都踩过)
下面这些坑,如果你踩到一个,就有可能直接炸内存。
1)一次性读太大的文件
经典坑:
file_get_contents('/var/log/300MB.log');
你以为是一个小文件,结果是 300MB!
PHP 直接爆。
2)一次性查询太多数据库记录
比如:
SELECT * FROM orders; // 十万行
然后你写:
$data = $pdo->query($sql)->fetchAll();
fetchAll() 会把所有内容一次性塞进内存,直接爆炸。
→ 正确做法:使用游标、分页处理
3)无限递归 / 死循环创建数组
下面这种写法经常炸:
$arr = [];
for ($i = 0; $i < 999999999; $i++) {
$arr[] = $i;
}
数组越堆越大,总有炸的一天。
4)json_decode 解析超大 JSON
比如你在处理一个 80MB 的 json 文件:
json_decode($json, true);
这个过程很费内存。
5)图片处理(imagecreatefromxxx)
使用 GD 库处理大图,内存消耗非常夸张:
- 一张 4000×4000 的 PNG 解码后会占几十 MB 内存
- 连续处理几张图片直接炸
6)XML、Excel、CSV 一次性加载
尤其是:
- PHPExcel(非常吃内存)
- SimpleXML
- DOMDocument
加载大文件 = 秒爆。
7)你用了 “无限追加” 日志记录
比如:
$log .= $message;
字符串在 PHP 中是复制的,不是引用,越加越大。
8)循环中重复创建对象
下面这种写法很容易出事:
for (...) {
$obj = new BigClass();
}
对象越来越多,内存也越来越吃紧。
9)使用 array_merge 合并巨大的数组
array_merge 会复制整个数组
如果 10W 项 × 10W 项,直接爆炸。
10)某些框架本身内存吃得多
最典型:
- Laravel
- Symfony
- WordPress
如果你用得不好,很容易出现:
Allowed memory size of 134217728 bytes exhausted
三、memory_limit 设置在哪里?怎么修改?
PHP 的 memory_limit 有 4 个常见修改方式。
方式 1:php.ini(最常用)
配置文件:
memory_limit = 512M
改完后重启服务器。
方式 2:Nginx + PHP-FPM 环境
修改 php-fpm 专属的 php.ini:
/etc/php/8.1/fpm/php.ini
重启:
systemctl restart php8.1-fpm
方式 3:代码里临时提高
ini_set('memory_limit', '512M');
适合某些特定任务(导出 Excel 等)。
方式 4:.htaccess(Apache)
php_value memory_limit 512M
四、内存溢出的最佳解决方案:不是加内存,而是“降内存”
很多人第一反应:
加到 1G、2G、4G 不就完事了?
其实这是错误的。
你应该先找原因,再加内存。
下面给你完整排查流程图(你要我生成 PNG 随时可以):
【图1】PHP 内存溢出排查流程图(适配百家号/CSDN)
┌──────────────┐
│ 内存溢出报错? │
└───────┬──────┘
↓
┌───────────────────┐
│ 查最近改过的功能? │
└────────┬──────────┘
↓
┌──────────────────────┐
│ 是否在操作大文件/大数组? │
└──────┬─────────────────┘
↓
┌─────────────────────────┐
│ 是 → 用流式处理、分页、逐行读取 │
└─────────────────────────┘
↓ 否
┌─────────────────────────┐
│ 检查死循环 / 递归 / array_merge │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 检查框架/库是否占用过高内存 │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 最后再考虑提高 memory_limit │
└─────────────────────────┘
五、实战:最常见的 5 类内存爆炸场景及解决方案
下面 5 个是百家号/CSDN 读者最爱看的实战案例。
场景 1:读取大文件(日志 / JSON / CSV)
❌ 错误写法:
$data = file_get_contents('bigfile.json');
✔ 正确方式:流式读取
$handle = fopen('bigfile.json', 'r');
while (!feof($handle)) {
$line = fgets($handle);
// 逐行处理
}
fclose($handle);
场景 2:fetchAll() 取 10W 行
❌ 错误:
$rows = $db->query($sql)->fetchAll();
✔ 正确方式:分页
$st = $db->prepare($sql);
$st->execute();
while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
// 处理每一行
}
场景 3:大数组拼接
❌ 错误:
$list = array_merge($a, $b);
✔ 正确:
foreach ($b as $v) {
$a[] = $v;
}
场景 4:图片处理吃内存
✔ 解决:
- 压缩原图
- 降低尺寸
- 启用 Imagick(比 GD 更省内存)
$img = new Imagick('big.png');
$img->resizeImage(1200, 0, Imagick::FILTER_LANCZOS, 1);
场景 5:PHPExcel 内存占用过高
✔ 解决:
使用 “低内存模式”:
$cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp;
$cacheSettings = ['memoryCacheSize' => '16MB'];
\PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);
或者换库:
- PhpSpreadsheet
- Spout(推荐,超省内存)
六、实战:怎么定位是哪一行代码吃内存?(小白也能用)
给你一个超实用的调试函数:
function debugMem($msg = '') {
echo $msg . ' = ' . round(memory_get_usage() / 1024 / 1024, 2) . " MB\n";
}
这样插:
debugMem("开始");
$arr = [];
for ($i = 0; $i < 50000; $i++) {
$arr[] = $i;
if ($i % 10000 == 0) debugMem("循环到$i");
}
你会看到内存变化,直接知道哪里吃内存。
七、什么时候应该提升 memory_limit?
下面这些场景可以放心加:
- 做 Excel 导出(尤其是几万行)
- 图片压缩服务器
- 大 JSON/日志处理
- 机器学习 / OCR / PDF 解析
如果是业务需要,你加到:
- 512M
- 1G
- 2G
都合理。
但如果普通接口都要用到 1G,那是代码问题,不是内存问题。 PHP 内存溢出看着可怕,其实只要搞懂两个核心:
- 为什么爆?因为超出 memory_limit 了。
- 怎么解决?不是加内存,而是把内存用得更高效。
只要你遵循:
- 不读大文件
- 不一次性查 5 万行数据
- 不用 fetchAll
- 不 array_merge 巨大量级数组
- 图片、Excel 用流式处理
你会发现:
PHP 并不是吃不了大任务,而是你以前不会用它。