workerman中new对象不unset会导致内存只涨不跌,因其进程常驻且对象生命周期绑定进程;需在onclose等时机主动unset、用weakmap替代静态缓存、避免闭包捕获$this、手动释放sdk资源,并结合gc_collect_cycles()排查隐式引用环。

Workerman里new完不unset,内存就只涨不跌
Workerman进程常驻,所有对象生命周期都绑定到Worker进程上。不像PHP-FPM每次请求结束自动重置,new出来的对象只要还有强引用(比如被静态数组存着、被闭包捕获、被定时器回调持有),就不会被GC回收——哪怕你已经“逻辑上不用它了”。memory_get_usage()可能看起来平稳,但gc_collect_cycles()一调,常暴露出几百上千个“该死没死”的对象。
- 典型场景:在
onMessage里new OrderService()处理订单,但没unset($service),下次同连接再发消息,又new一个 - 更隐蔽的是:用
static $instances = [];做单例缓存,却忘了在onClose里unset(static::$instances[$connection->id]) - PHP 8.0+ 推荐改用
WeakMap:它不阻止GC,对象无其他引用时立刻释放,比static array安全得多
gc_collect_cycles()不是万能解药,但不用它一定出事
PHP默认的GC触发是被动且延迟的,依赖引用计数归零或内存压力阈值。Workerman这种长连接服务几乎从不触发自动GC,尤其当存在循环引用(如模型A→事件监听器→模型A)时,不手动干预就会越积越多。
- 别只在脚本开头写
gc_enable(),还要在关键节点调用gc_collect_cycles():比如每处理100条消息后、每次onClose清理完资源后 - 检查是否误调了
gc_disable()——某些SDK或旧代码会关掉GC,等于主动放弃防线 -
gc_collect_cycles()返回值有意义:返回0说明没清理到东西,可能是真没泄漏;返回几百上千,就得顺着xdebug_debug_zval()查refcount和is_ref了
闭包和定时器是内存泄漏高发区
在Workerman里写Timer::add(1, function() { $this->doSomething(); }),等于给当前实例加了一条无法被GC穿透的强引用链。这个闭包会一直持有$this,哪怕连接早已断开、业务逻辑也结束了。
- 定时器回调优先用静态方法:
Timer::add(1, [self::class, 'tick']),或独立函数 - 必须用实例方法时,记得在
onClose里调用Timer::del($timerId)显式销毁 - 避免
use ($connection)或use ($request)——整个TcpConnection对象可能带一堆socket资源和事件句柄,远超你需要的数据
第三方SDK不close,等于留了个内存后门
Redis客户端、HTTP SDK、数据库连接池……这些不是“用完即焚”的短命对象。它们内部常驻着连接池、定时器、回调注册表。Workerman不会替你关,PHP GC也收不回它们持有的底层资源。
- Redis实例建议懒加载 + 每次使用前检查
$this->redis->isConnected(),断连后unset($this->redis) - EasyWeChat、阿里云SDK等,务必调用其
close()、destroy()或明确文档里的清理方法 - 别信“析构函数会自动执行”——常驻进程中
__destruct()基本不触发,必须手动
unset,而是判断“这个对象现在到底还该不该活着”。很多泄漏发生在异步回调、状态机跳转、多设备登录映射等边界场景里,变量存活期比你想的长得多。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











