PHP 内存溢出到底怎么回事?memory_limit 限制与性能优化(新手也能看懂)

68 阅读3分钟

一句话总结:
只要你的 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 并不是吃不了大任务,而是你以前不会用它。