Xdebug 的 memory 模式必须显式启用,仅开启 profile 或 debug 不会记录内存分配;需在 php.ini 中设置 xdebug.mode=develop,memory,确保 zend_extension 路径正确、xdebug.output_dir 可写,并用 php -i 验证;CLI 下推荐手动调用 xdebug_start_memory_profile() 打点,生成 memprof_*.out 文件后用 memprof_analyser.php 分析,结合堆快照(xdebug_create_memory_snapshot)和 gc_collect_cycles 判断真实泄漏。

Xdebug 的 memory 模式必须显式启用,只开 profile 或 debug 不会记录内存分配行为,也抓不到泄漏点。
确认 php.ini 中已启用 xdebug.mode=develop,memory
很多用户误以为只要开了 Xdebug 就能分析内存,其实 Xdebug 3 要求明确声明 memory 才会采集堆分配数据。仅写 xdebug.mode=profile 或 debug 是无效的:
-
xdebug.mode必须包含memory,且推荐加上develop(提供更友好的var_dump()输出和错误上下文);不能写成profile,memory——profile与memory是互斥模式,同时启用会导致内存快照被忽略 - 确保
zend_extension指向正确的.so(Linux/macOS)或.dll(Windows)文件,路径需绝对且 PHP 进程可读 -
xdebug.output_dir必须设为一个 PHP 进程有写权限的目录,例如/tmp/xdebug,并提前执行mkdir -p /tmp/xdebug && chmod 777 /tmp/xdebug - 重启 PHP-FPM 或 Apache 后,用
php -i | grep xdebug.mode验证输出是否为develop,memory
CLI 脚本中用 xdebug_start_memory_profile() 手动打点
相比自动触发(xdebug.start_with_request=yes),手动打点更适合定位具体逻辑块的内存增长,避免全量快照干扰判断:
- 在脚本开头加
xdebug_start_memory_profile();,在关键循环/函数调用前后、或疑似泄漏位置插入xdebug_stop_memory_profile();并立即调用xdebug_start_memory_profile();形成对比段 - 若脚本可能异常退出,务必在
try-catch-finally的finally块中调用xdebug_stop_memory_profile(),否则快照不生成 - 运行后检查
xdebug.output_dir下是否生成memprof_*.out文件(不是cachegrind.out.*);没有该文件说明memory模式未生效或写入失败 - 不要依赖 PhpStorm 的「Run」按钮直接执行——它默认不传
-dxdebug.mode=memory,应改用终端命令:php -dxdebug.mode=memory script.php
解析 memprof_*.out 文件找高耗函数
生成的 .out 文件是纯文本格式,但直接阅读效率低;官方推荐用 memprof_analyser.php 工具排序分析:
立即学习“PHP免费学习笔记(深入)”;
- 下载 memprof_analyser.php(Xdebug 官方维护),放在任意可执行目录
- 执行:
php memprof_analyser.php /tmp/xdebug/memprof_123456789.out | head -20,按Total memory allocated降序查看前 20 行 - 重点关注
include、require、__construct、json_decode、file_get_contents等易引发内存堆积的调用点 - 如果某函数
Total memory allocated极高但Call count很低,大概率是单次分配过大(如加载大文件);若Call count高而Memory per call稳定上升,则是循环中未释放引用(比如对象池未清理)
比对堆快照确认对象泄漏(xdebug_create_memory_snapshot())
当 memprof 指向某个类或方法时,进一步用堆快照验证实例数是否持续增长:
- 在循环开始前调用
$s1 = xdebug_create_memory_snapshot();,循环结束后调用$s2 = xdebug_create_memory_snapshot(); - 两个快照会生成
.zsnap文件,用php -r "print_r(xdebug_get_memory_snapshot_diff('$s1', '$s2'));"查看差异 - 重点看
class_name和instances字段:如果某个类的instances数量随循环轮次线性增加,且无对应unset()或__destruct()调用,基本可断定是循环引用或全局缓存未清理 - 此时配合
gc_collect_cycles()主动触发垃圾回收,并观察后续快照中该类实例是否回落——不回落即证实泄漏存在
真正难的不是生成快照,而是区分「内存使用量大」和「内存泄漏」:前者是正常业务负载,后者是分配后永不释放。所以务必在稳定复现的最小代码片段中做对比,跳过框架初始化、Composer autoloader 等干扰层;另外,memory_get_usage(true) 比 Xdebug 快照更轻量,适合在循环内高频打点验证趋势。


















