PHP Allowed memory size exhausted:真泄漏还是配额太小

来源:互联网 时间:2026-09-06

日志里刷 Fatal error: Allowed memory size of 134217728 bytes exhausted,后台某个导出功能一跑就挂。134217728 字节就是 128M,这是 php.ini 里 memory_limit 的官方默认值。很多人第一反应是把数字改大,先别急——内存溢出有两种完全不同的病:一次性的大任务撞了天花板,和代码真泄漏,处理方式南辕北辙。

区分方法:看报错的 URL 和频率。如果只有那个导出、批量图片处理的大接口报错,其他页面正常,多半是任务本身需要更多内存,配额太小;如果是随机页面、越报越多,甚至刚启动没几秒就报,那是泄漏,改大配额只会让进程死得更慢,最终把 FPM 整体拖垮。

# 先看当前生效的 memory_limit(注意 CLI 与 FPM 是两套配置)
php -i | grep memory_limit

# 在嫌疑代码里打点,看真实峰值离天花板有多远
echo memory_get_peak_usage(true) / 1048576 . " MB peak\n";
// peak 打到 120MB 以上而 limit=128M,就是任务真需要更多内存

如果确认是任务型需求,两条路:加配额,或者降内存占用。加配额有讲究,FPM 下每个 worker 进程都持有这个上限,pm.max_children 乘以 memory_limit 不能超过物理内存的七成,否则就是批量制造 OOM。降占用往往才是根治:把“一次读进内存再处理”改成“边读边处理”,PHP 的生成器就是干这个的。

// 反面教材:20 万行 CSV 一次性读进来,内存直接爆
$rows = file('big.csv'); // 全量加载
foreach ($rows as $row) { /* ... */ }

// 正确姿势:生成器逐行产出,内存恒定在 KB 级
function readCsv(string $path): Generator {
$fh = fopen($path, 'rb');
while (($row = fgetcsv($fh)) !== false) {
yield $row;
}
fclose($fh);
}
foreach (readCsv('big.csv') as $row) { /* ... */ }

// 大数组用完随手 unset,循环里复用变量而不是累加

如果怀疑泄漏,重点查三个惯犯:长循环里 new 对象不释放、把数据库大结果集全量 fetch 后缓存进静态变量、还有闭包里 capture 大变量的意外引用。定位手段是在循环里周期性打印 memory_get_usage,观察是否单调上涨不回落——每轮涨一点且不归零,就是泄漏实锤。

另外记住一条:memory_limit 设成 -1(不限制)在某些老教程里出现过,千万别用于生产。PHP 官方手册明确写了这是“防止写得差的脚本吃光服务器内存”的保险丝,拆了保险丝,下一个 Fatal 就是 OOM Killer 替你重启 FPM。改配额的正确顺序永远是:先测峰值、再算总量(max_children × limit)、最后动 php.ini 并重启 FPM 生效。

相关阅读:内存报错横跨应用层和系统层。《服务器故障排查总纲:按现象反查的五层定位法》把 FPM 耗尽、OOM Kill 和本篇排成一条内存问题的排查链,先定层再动手,效率高得多。

相关文章

标签:

A5创业网 版权所有