PHP 8.1 下用 Xdebug 查内存泄漏,我踩了三天坑

This post is available in 简体中文 only — the original follows.

PHP 8.1.2,CLI 模式,跑一个批量消费队列的脚本。数据量不大,单次处理 500 条左右,但脚本常驻,每 10 秒拉一次。跑着跑着内存就上去了,从 30M 涨到 200M 只用了半小时,最后被 OOM 杀掉。

我先用 memory_get_usage 打点,发现内存涨在循环里。但用 unset 释放变量没用,怀疑是循环引用。于是上 Xdebug,装的是 3.1.6,php.ini 里开了 xdebug.mode=develop 和 xdebug.start_with_request=yes。一开始瞎调,直接上 xdebug_debug_zval 看数组,结果输出太多,根本看不清。

后来改成在循环末尾抓一次快照:

if ($i % 100 === 0) {
    file_put_contents('/tmp/xdebug_dump_' . $i . '.txt', xdebug_debug_zval('items'));
}

但这样只能看一个变量,不够。又试了 xdebug_memory 分析,官方文档说用 xdebug.mode=profile 可以生成 callgrind 文件,再用 kcachegrind 看。我照着做了,发现耗时最多的是 json_decode,但内存分配看不出来。

折腾到第二天,想起 PHP 有 gc_collect_cycles,手动调了一次,内存没降,说明不是循环引用。那问题可能在扩展或者长生命周期对象。我用 get_defined_vars 对比每轮循环的变量,发现有个全局的容器实例在往数组里塞东西,每轮都 append,从不清理。

改法很简单:在循环末尾把容器里的缓存数组清掉,或者改用 WeakMap 存临时数据。改完内存稳定在 35M 左右。

这个坑让我明白:Xdebug 的 zval 调试适合看单点,不适合追长跑脚本。内存泄漏先分清是 PHP 变量层还是扩展层,gc_collect_cycles 能排除循环引用,剩下的就靠变量快照对比。另外,常驻脚本一定要有内存上限监控,不能等 OOM 才发现。

你们在 CLI 常驻脚本里,是怎么定位那种缓慢增长的内存问题的?

0
Approval 100% · 0 comments · 4 Reads

0 comments

Markdown supported · replies push in real time

No comments yet — be the first.