workerman4定时器未销毁导致内存泄漏,表现为rss持续上涨、memory_get_peak_usage(true)线性上升而memory_get_usage()稳定;根源是定时器回调强引用对象阻碍php gc回收,需通过进程监控、峰值日志、gc触发、id追踪及weakmap管理定位并清理漏删定时器。

Workerman4 定时器没销毁导致的内存泄漏,核心表现是:Worker 进程 RSS 持续上涨、memory_get_peak_usage(true) 线性上升、但 memory_get_usage() 看似稳定——因为泄漏对象被定时器回调强引用着,PHP GC 无法自动回收。
看进程内存趋势,确认是不是真泄漏
别只盯着单次 memory_get_usage()。用系统命令持续观察:
-
watch -n 5 'ps aux --sort=-%mem | head -10 | grep worker'—— 找出 RSS 涨得最猛的那个 Worker PID - 在该进程里加日志:每次业务逻辑结束前打点
echo "peak: " . memory_get_peak_usage(true) . "\n",对比多轮请求是否逐次升高 - 手动触发 GC:
gc_collect_cycles(); gc_mem_caches();后再看 RSS 是否回落,不回落就是有强引用钉住对象
查定时器注册源头,定位漏删位置
Timer::add() 返回的 ID 是唯一追踪线索。重点检查三类代码:
-
onConnect/onMessage 中动态创建的定时器:比如连接后启心跳、收消息后设超时清理,但没在
onClose或状态变更时调Timer::del($id) -
闭包捕获
$this的定时器:如Timer::add(5, [$this, 'checkStatus']),这个绑定会让整个 Worker 实例无法释放 -
全局或静态变量里存了 timer ID 却未清理:比如
self::$timers[$connId] = Timer::add(...),但onClose里只 unset 连接没 del 定时器
用 WeakMap + 日志辅助追踪(PHP 8.0+)
临时加一层“可审计”的定时器管理:
- 定义
private static WeakMap $activeTimers;,每次Timer::add()后存:self::$activeTimers[$timerId] = ['conn_id' => $conn->id, 'desc' => 'heartbeat'] - 在
onClose或关键退出路径里,遍历self::$activeTimers并Timer::del(),同时清空对应项 - 加个调试接口,输出
count(self::$activeTimers),上线后用 HTTP 请求实时查看还有多少活跃定时器
用 Xdebug 快照抓循环引用链
当怀疑某个定时器回调持有了不该持有的大对象时:
- 启用 Xdebug(需编译支持),在疑似泄漏点前后各打一次
xdebug_debug_zval('your_var'),看refcount是否异常高 - 生成 PHP Heap Snapshot:
xdpmemprof_start(); ... 业务执行 ... xdpmemprof_stop();,用分析工具看哪些对象被Timer::add回调长期持有 - 重点关注 retaining path 里是否出现
Closure → WorkerInstance → static::$cache → Connection这类长链











