Webman内存持续上涨OOM是因隐式强引用导致RSS不回落,非unset可解;memory_get_usage仅反映PHP堆内存,需用ps查RSS并结合gc_collect_cycles验证泄漏。

Webman 进程长时间运行后内存持续上涨、最终 OOM 宕机,基本可以确定是 PHP 长驻进程的隐式引用泄漏,不是简单 unset 能解决的。
为什么 memory_get_usage() 看不出问题?
这个函数只返回当前脚本分配的内存(PHP 堆内),但 Webman/Swoole 的真实内存压力来自:rss(常驻集大小)——即操作系统看到的物理内存占用。GC 清理了对象,但若某个闭包、静态变量、全局容器或事件监听器还强持有对象,rss 就不会回落。
- 用
ps aux --sort=-%mem | head -10查看进程RSS值,观察是否随请求/定时任务次数线性增长 - 在关键逻辑后手动调用
gc_collect_cycles(); gc_mem_caches();,再看RSS是否下降 —— 不降,说明有强引用钉住内存 - 不要依赖
memory_get_usage(true)做判断,它对长驻进程几乎失效
哪些代码结构最容易藏匿泄漏?
不是“没写 unset”,而是你根本没意识到自己创建了不可见的持有链。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
-
static $cache = [];未加清理机制,key 是用户 ID 或时间戳,越积越多 - 闭包里用了
use ($this)或use (&$data),导致整个服务容器或大数组被生命周期延长 - 注册了事件监听器(如
Event::on('job.done', function () { ... }))但从未Event::off() - 定时任务中用了
foreach (&$item),循环结束后忘记unset($item),最后一个元素被全局变量引用 - 日志处理器(如 Monolog 的
FingersCrossedHandler)缓存了大量未触发的记录,尤其在 debug 模式下
怎么快速验证是不是泄漏?
别等宕机,用最小干预手段做“压力-回收”对照测试。
- 启动 Webman 后,记下初始
rss(比如 42MB) - 连续发起 100 次相同接口请求(用
ab -n 100 -c 10 http://localhost/api/test) - 请求结束后立即执行
kill -USR2 {pid}(触发 Webman reload)或手动gc_collect_cycles()+ 等待 5 秒 - 再查
rss:如果从 42MB → 68MB → 仍卡在 65MB 不回落,就是泄漏;如果回落到 45MB,大概率是 GC 滞后或缓存合理增长 - 配合
php --ri opcache确认 OPcache 未开启opcache.enable_cli=1(CLI 模式下开它会导致内存无法释放)
定位泄漏对象的实操路径
靠猜不行,得看堆里谁在“赖着不走”。PHP 8.0+ 可直接用内置工具,不用装扩展。
- 用
php -r "gc_disable(); xdebug_debug_zval('your_var_name');"查单个变量引用计数(需启用 xdebug) - 更推荐:用
php -r "print_r(get_defined_vars());"在关键位置 dump 全局变量(慎用,仅调试) - 终极手段:生成堆快照:
php -r "gc_disable(); file_put_contents('/tmp/heap.json', json_encode(xdebug_get_function_stack(), JSON_PRETTY_PRINT));",再人工比对重复出现的大数组/对象路径 - 注意:Swoole/Webman 中
$server->on('request', function () { ... })的回调函数本身会被长期持有,所有use的变量都会延长生命周期
真正麻烦的从来不是“内存涨了”,而是“为什么涨了却收不回去”——那个看不见的引用持有者,往往藏在你最信任的封装逻辑里。盯住 rss,而不是 memory_get_usage,才能避开绝大多数假阳性判断。

















