先用memory_get_usage(true)在关键节点监控并跑多轮验证增长趋势,再用xdebug生成.memprof_.out快照分析高耗函数,必要时用valgrind检查c扩展层malloc泄漏,并手动解除循环引用、及时释放资源句柄。

memory_get_usage(true) 快速定位增长点
别等脚本崩了才看内存,先在 CLI 下跑几轮,用 memory_get_usage(true) 打点监控真实分配量。加 true 是关键——它绕过 Zend 内存池缓存,数值更准;不加的话可能看到的是“假稳定”。
常见打点位置:
- 函数入口和出口
-
$pdo->query()前后 -
file_get_contents()或fopen()操作前后 - 循环体开始前、每次迭代末尾(尤其 for/while 处理大批数据时)
跑 50–100 轮相同逻辑,记录每次 memory_get_usage(true) 差值。若每轮稳定上涨(比如 +6KB),基本可锁定该段代码存在泄漏。注意:单次结果没意义,趋势才是证据。
Xdebug 生成 .memprof_.out 快照分析高耗函数
确认增长点后,必须进堆栈看谁在分配、谁在持有。PHP 8.1 默认不启用内存分析,得手动配好再跑:
确保 php.ini 中有:
zend_extension=xdebug.so xdebug.mode=develop,memory xdebug.output_dir="/tmp/xdebug"
目录 /tmp/xdebug 必须存在且 PHP 进程有写权限。脚本里加:
xdebug_start_memory_profile(); // ... 你的可疑逻辑 xdebug_stop_memory_profile();
运行后会在 /tmp/xdebug/ 下生成 memprof_*.out 文件。用官方 memprof_analyser.php 解析(PHP >= 8.0 可直接运行),按 “Total memory allocated” 排序,排第一的函数就是最可疑的内存大户。
避坑提示:如果脚本可能异常退出,务必把 xdebug_stop_memory_profile() 放在 finally 块里,否则快照写不全。
手动断开循环引用 + 调用 gc_collect_cycles()
PHP 的 GC 对简单循环能处理,但高并发或长生命周期对象下容易延迟甚至失效。典型泄漏结构是:$a->b = $b; $b->a = $a; —— 即使 unset($a, $b),只要任一属性还被其他变量间接引用,GC 就不会触发。
修复不是靠等 GC,而是主动干预:
- 在业务逻辑结束前,显式置空双向引用:
$a->b = null; $b->a = null; - 闭包中慎用
use ($bigObj),用完立刻$bigObj = null; - 静态缓存要设上限,比如
static $cache = [];改成static $cache = []; if (count($cache) > 100) array_shift($cache); - 调用
gc_collect_cycles()强制触发一轮回收,但别滥用——它本身有开销,只在关键释放点后调一次即可
验证是否生效:在断开引用 + gc_collect_cycles() 后,再打一次 memory_get_usage(true),看是否回落。
别漏掉 gc_mem_caches() 清理内存碎片
有些场景下,unset() 和 gc_collect_cycles() 都做了,内存还是不降——大概率是小内存块(gc_mem_caches(),专门清理 Zend 内存管理器里的小块缓存,它不归还给 OS,但能重用,避免持续向系统申请新页。
适用场景:
- CLI 常驻进程(如 Swoole、ReactPHP)
- 大量短生命周期字符串、小数组反复构造/销毁
- 用
memory_get_usage(true)看到持续缓慢上涨,但 Xdebug 快照里找不到明显大对象
建议搭配定时器使用,例如 Swoole 中每 30 秒调一次:gc_mem_caches();。注意:这个函数在 FPM 请求结束后自动发生,所以 Web 场景一般不用手动调。
最后提醒一句:内存泄漏排查最易卡在「以为修复了,其实只是缓存没刷出来」。一定要在相同环境、相同数据、相同轮数下对比前后 memory_get_usage(true) 的差值趋势,而不是只看某一次的绝对值。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











