workerman内存持续上涨而memory_get_usage()无明显变化,是因为该函数仅统计脚本堆内存,不反映闭包捕获、静态变量强引用、第三方sdk缓存及libevent等c层内存;需检查gc_disable()、静态变量生命周期、闭包隐式引用和weakmap替代方案。

Workerman内存持续上涨却看不到明显对象堆积,是因为PHP引用计数未及时回收,导致逻辑上已死亡的对象仍被闭包、静态变量或全局容器强持有,底层socket资源和扩展内存也不释放。
为什么memory_get_usage()看起来没涨,内存却越跑越高
这个函数只统计当前脚本分配的堆内存,不包含被闭包捕获的上下文、静态属性持有的Connection对象、第三方SDK内部缓存,更不反映libevent或sockets扩展占用的C层内存。
比如一个TcpConnection对象被onMessage里的匿名函数捕获,该函数又绑定在类实例上,类实例又被存进self::$cache——这条强引用链会让整个对象图无法被GC扫描到。
【gc_disable()调用会彻底关闭PHP垃圾回收】,Workerman默认开启GC,但某些SDK或启动脚本可能误加这一行,必须检查入口文件。
三个最常踩的泄漏源头
第一步:查静态变量是否只增不减
打开Worker类或业务逻辑类,搜索static $关键字。常见陷阱是self::$uidConnections[$connection->id] = $connection写入后,onClose里只unset($connection),却忘了unset(self::$uidConnections[$connection->id])。
第二步:看闭包是否隐式持有了大对象
Timer::add(1, [$this, 'tick'])比function() use ($largeData) {}更危险——前者给整个类实例加了一条强引用,GC永远无法回收。
第三步:验第三方SDK是否留了僵尸实例
ThinkPHP的Db门面在Workerman中复用Container单例,每次Db::name()都会往$container->getInstances()里塞新Query对象,但不会自动清理。运行1000次后,getInstances()返回数组可能含上万个残留实例。
PHP 8.0+ 必须改用WeakMap的场景
方法一:替换静态连接缓存
把private static $connections = [];换成private static $connections = new WeakMap();,然后self::$connections[$connection] = $context;——连接断开后,$connection对象被销毁,WeakMap自动解绑。
方法二:避免用Connection对象本身作数组键
错误写法:self::$cache[$connection] = $data; → $connection被数组强引用,即使onClose触发,unset($connection)也无效。
正确写法:self::$cache[$connection->id] = $data;,键必须是int/string等可销毁标量。
快速定位泄漏进程的命令
执行ps aux --sort=-%mem | head -10,找出RES列最高的Worker进程PID。
进入该进程工作目录,运行php -r "echo memory_get_peak_usage(true);"打点对比——重点不是当前值,而是连续5分钟内是否线性上升。
若确认泄漏,立即在该子进程业务逻辑末尾插入gc_collect_cycles();并记录memory_get_peak_usage(true),观察增幅是否收窄。











