workerman内存持续升高而memory_get_usage()无明显变化,主因是php引用计数未及时回收,存在循环引用、闭包捕获、静态变量等导致逻辑死亡对象未释放;需手动gc_collect_cycles()、用weakmap替代静态数组、清理onclose资源并借助xdebug_debug_zval()分析引用链。

Workerman内存持续升高,memory_get_usage()却没明显变化?这基本不是“内存没涨”,而是PHP引用计数未清、对象逻辑死亡但强引用链还在——得靠gc_collect_cycles()+xdebug_debug_zval()才能揪出来。
为什么memory_get_usage()看起来稳定,内存却越跑越高?
因为这个函数只统计当前脚本堆内存,不反映被闭包、静态属性、全局变量意外持有的“僵尸对象”。长连接场景下,一个TcpConnection对象被回调闭包捕获,又反过来持有$this,再指向self::$cache,就构成循环引用。GC默认不触发回收,memory_get_usage()自然“看不见”。
- 每次业务逻辑结束前手动加
gc_collect_cycles(),再用memory_get_peak_usage(true)打点对比 - 别信
memory_get_usage()单次值,重点看memory_get_peak_usage()随时间是否线性上升 - 检查入口文件是否误调了
gc_disable()——Workerman默认开启GC,关了等于放弃防线
静态变量和连接缓存怎么清理才安全?
Workerman进程常驻,static $connections或self::$cache一旦写入不清理,就只增不减。尤其不能用$connection自身作数组键,它可能被其他闭包强引用,导致unset()无效。
- 在
onClose里必须配对unset(self::$connections[$connection->id]),键必须是可销毁的标量(如ID) - PHP 8.0+ 优先改用
WeakMap:$map = new WeakMap(); $map[$connection] = $context;,连接断开后自动解绑 - 避免在
onMessage或定时器里直接往静态数组[] =塞数据,除非你明确控制其生命周期
闭包、定时器和第三方SDK是泄漏高发区
闭包会隐式捕获作用域变量,Timer::add(1, [$this, 'tick'])比匿名函数更危险——它给实例加了一条强引用链,GC无法回收。
- 定时器回调尽量写成静态方法或独立函数,别在类方法内定义闭包并引用
$this - 连接关闭或状态切换时,务必调用
Timer::del($timerId)主动销毁不再需要的定时器 - Redis/HTTP SDK/数据库连接等必须手动释放:
unset($this->redis)前先检查$this->redis->isConnected();EasyWeChat、阿里云SDK等要显式调用close()或destroy()
怎么快速定位泄漏源头?
别靠猜。先用ps aux --sort=-%mem | head -10确认是哪个Worker进程在涨,再结合工具精准打击。
- 用
xdebug_debug_zval('your_var')查可疑对象的refcount和is_ref,确认是否被意外持有 - 启用
swoole_tracker(需编译时开启tracker.enable_malloc_hook=1),捕获malloc/free不匹配 - Hyperf用户可用内置
Profiler组件查单次请求内存峰值;Workerman项目可加日志输出posix_getpid(),把内存打点和Worker ID绑定
最易被忽略的一点:SwooleTable或Redis连接池混用时,很多人忘了Workerman子进程各自初始化一份,new Swoole\Table必须严格限定在Worker::onWorkerStart里,且只通过global $table访问——在onMessage里重复new,等于每个请求都造一个新表。











