hyperf内存持续升高基本可断定为内存泄漏,需用swoole_tracker捕获malloc/free不匹配,结合ps aux观察rss线性上涨趋势,重点排查dispatch_mode配置、context未清理、静态变量累积及c扩展内存持有问题。

Hyperf项目部署后内存持续升高,基本可以断定是内存泄漏,而不是“偶发卡顿”或“瞬时高峰”。关键不是猜原因,而是快速确认泄漏是否存在、定位到具体模块——swoole_tracker 是目前最直接有效的工具,它能捕获 C 层 malloc/free 不匹配,而这是 memory_get_usage() 和 PHP 日志完全看不到的盲区。
看 RSS,别信 memory_get_usage()
协程常驻进程下,memory_get_usage(true) 只反映 Zend 堆内存,漏掉了协程栈、Redis/PDO 扩展持有的 C 层内存、Swoole 自身 malloc 分配。你看到“用了 12MB”,实际 ps aux 显示 RSS 已涨到 320MB——差额就是泄漏本体。
- 用
ps aux --sort=-rss | head -5每 30 秒观察,RSS 是否线性上涨 - 在请求末尾加
gc_collect_cycles(); gc_mem_caches();,等 5 秒再看 RSS 是否回落;不回落,说明存在强引用未断(比如闭包捕获大对象、静态变量存了 Request 实例) - 不要依赖日志里打印的
memory_get_usage()值做判断,它对泄漏排查几乎无效
swoole_tracker 配置必须关掉 tracker.enable
tracker.enable 和 tracker.enable_malloc_hook 不能同时开启,否则 hook 失效。线上复现阶段必须关闭总开关,只启用 malloc hook 和 memcheck。
- 正确配置片段:
tracker.enable=0、tracker.enable_malloc_hook=1、tracker.enable_memcheck=1 - 采样率别设 100:调试时用
tracker.sampling_rate=10,上线环境严禁全量采样,否则性能暴跌 - 泄漏日志默认输出到
/tmp/swoole_tracker.log,典型行如[leak] addr=0x7f8b4c0a1234 size=16384 at ext/redis/redis.c:1234,直接定位到 C 文件行号
Worker0 内存独高?先查 dispatch_mode
如果只有 Worker0 RSS 持续飙升,其他 Worker 正常,大概率不是代码泄漏,而是配置误配导致流量全部打到 Worker0。
- 检查
server.settings.dispatch_mode:值为2(即SWOOLE_DISPATCH_QUEUE)时,所有请求会发往 Worker0,造成单点内存暴涨 - 应改为
1(SWOOLE_DISPATCH_FDMOD)或3(SWOOLE_DISPATCH_IPMOD),让负载均衡分发 - 加日志时务必带上
posix_getpid(),避免多 Worker 日志混杂失焦,例如:$this->logger->info('SQL executed', ['pid' => posix_getpid(), 'sql' => $sql])
协程退出 ≠ 内存释放,Context 和静态变量是重灾区
协程结束时,栈内存自动回收,但堆内存里的 Context 数据、静态属性、未 close 的协程句柄仍长期驻留。
-
Context::set('big_data', $hugeArray)后没配defer清理,协程退出后数据还在 - 类中定义
public static $cache = [],每次请求都$cache[] = $item,越积越多 - 自定义进程中用
Coroutine::create()启协程,却忘了Coroutine::close()收尾 - 验证方式:在关键节点插入
echo memory_get_usage(true), "\n";,但仅作辅助,最终以 RSS 变化为准
真正难排查的泄漏,往往藏在 C 扩展调用链里(比如 Redis::hGetAll 返回大数组后被静态变量持有),swoole_tracker 是唯一能穿透 PHP 层直达 malloc 调用栈的工具。别在 Hyperf 日志里翻来覆去找“谁没 unset”,要去看协程真实生命周期和内存分配路径——那才是泄漏发生的现场。











