Workerman中静态变量不清理会导致内存持续上涨,因其生命周期与进程一致;需在onClose等时机主动unset、用WeakMap替代、避免闭包捕获$this、手动释放SDK资源,并结合gc_collect_cycles()与xdebug_debug_zval()精准排查隐式引用环。

Workerman里静态变量不清理就会一直涨内存
Workerman进程常驻,static 变量生命周期与进程一致。如果在 onMessage 或 onConnect 里往 self::$cache、static $connections 里不断 push 或 []=,又没在对应 onClose 或业务结束时 unset,内存就只增不减——哪怕 memory_get_usage() 看起来稳定,gc_collect_cycles() 后也可能暴露出大量“逻辑死亡但引用未断”的对象。
- 所有静态数组/对象缓存必须配对清理:连接关闭时用
unset(self::$connections[$connection->id]),别用$connection自身作键(它可能被闭包强引用) - 避免在回调中捕获
$this或大对象,改用参数传递或弱引用 - PHP 8.0+ 优先用
WeakMap替代static array存连接上下文,例如:$map = new WeakMap(); $map[$connection] = $context; - 检查入口文件是否误调用了
gc_disable(),Workerman 默认开启 GC,关了等于自废武功
闭包和定时器是隐式内存泄漏高发区
闭包会自动捕获所在作用域的变量,定时器回调(如 Timer::add)若定义在类方法内且引用了 $this,就等于给当前实例加了一条强引用链,GC 无法回收。这类泄漏在长连接场景下特别隐蔽。
- 定时器回调尽量写成静态方法或独立函数,避免闭包捕获实例:
Timer::add(1, [$this, 'tick'])比Timer::add(1, function() { $this->doSomething(); })更危险 - 使用
Timer::del($timerId)主动销毁不再需要的定时器,尤其在onClose或状态切换时 - 用
xdebug_debug_zval()查看可疑对象的refcount和is_ref,确认是否被闭包、静态属性或全局变量意外持有
第三方 SDK 和连接对象要手动断开
Workerman 不像 PHP-FPM 那样每次请求后重置环境,Redis 客户端、HTTP SDK、数据库连接等一旦初始化,就会常驻进程内存。很多 SDK 内部持有回调、超时定时器、资源句柄,不显式释放会导致内存持续累积。
- Redis 实例建议懒加载 + 静态属性,每次使用前检查
$this->redis->isConnected(),断连后unset($this->redis) - EasyWeChat、Alibaba Cloud SDK 等务必调用其
close()、destroy()或__destruct()方法(如有),不要依赖 GC - SwooleTable 若混用,必须在
Worker::onWorkerStart中创建,且仅限该回调内通过global $table访问,禁止在onMessage里重复new Swoole\Table
什么时候该手动触发 GC?
PHP 的 GC 是异步且非实时的,Workerman 的高频请求场景下,靠默认策略容易滞后。不能等 OOM 才行动,得主动干预。
- 在每轮关键业务逻辑结束时(如
onMessage处理完响应后)调用gc_collect_cycles() - 配合
memory_get_peak_usage(true)打点记录峰值,对比前后值判断是否真有泄漏 - 避免在高频循环内频繁调用 GC,可在每 N 次请求或固定时间间隔(如 30 秒)触发一次
- 注意:GC 只能清理循环引用,对普通强引用无效——所以清理源头(
unset、close、del)永远比等 GC 更可靠
unset,而是对象之间形成的隐式引用环:连接 → 回调闭包 → 类实例 → 静态缓存 → 连接……这种链式持有,靠 gc_collect_cycles() 也未必能及时斩断。动手前先用 xdebug_debug_zval() 或 debug_zval_dump() 抽样几个疑似对象,看清 refcount 才能找准下手点。

















