hyperf内存占用过高主因是协程生命周期管理不当、context未清理及swoole配置不合理;worker0内存异常飙升多因首请求触发长协程、自定义进程死循环、context::set未配defer、max_request=0导致内存累积,需设max_request、显式gc_collect_cycles并优化协程栈与gc时机。

Hyperf内存占用过高,大概率不是PHP本身的问题,而是协程生命周期、Context引用或Swoole运行时配置没对齐导致的资源滞留。
Worker0内存异常飙升的典型表现
用ps aux看到只有Worker0 RSS持续上涨,其他Worker稳定——这基本排除全局内存泄漏,指向调度/负载/上下文绑定问题。
- 常见诱因是HTTP请求被长期卡在某个协程里没退出,比如未设超时的Redis连接、没
defer清理的Context大对象、或Task Worker里漏掉Coroutine::sleep()的轮询逻辑 - Hyperf默认把首个HTTP请求分发给Worker0,如果该请求触发了长协程(如导出百万行Excel),它会一直占着Worker0的栈和堆空间,直到协程结束
- 检查
hyperf/process自定义进程是否在handle()里用了while(true)但没Coroutine::sleep(),这类代码会让Worker0死循环榨干CPU+内存
Context::set()不配defer就是内存泄漏温床
协程退出时,Context里的数据不会自动释放——哪怕你只存了一次Context::set('user', $bigArray),只要没显式清理,这块内存就跟着协程栈一起“活埋”在Worker进程里。
- 错误写法:
Context::set('data', $hugeResult); // 协程结束后仍驻留 - 正确写法:
defer(fn () => Context::del('data'));或更稳妥地用try/finally包裹 - 注意
Coroutine::fork()会复制父协程Context,若父协程Context已存大对象,子协程一创建就继承了内存负担
Swoole运行时配置直接影响内存驻留周期
默认max_request => 0看似省事,实则让Worker进程永不死——所有协程残留、静态变量、未释放的Redis连接全堆在同一个进程里,时间越久内存越高。
- 必须设
max_request => 100000(参考值),配合reload_async => true实现平滑重启,让内存定期“归零” -
enable_coroutine => true要全局开启,否则遇到file_get_contents()这种同步调用,整个Worker会被阻塞挂起,协程调度器无法回收其栈内存 - 禁用
opcache.enable_cli=1(CLI模式下OPcache可能干扰协程GC),改用opcache.memory_consumption=256并确保opcache.max_accelerated_files足够大
协程栈大小与GC触发时机需要手动干预
Swoole默认协程栈8KB,但实际业务中常因递归深度或大数组触发栈扩容,一旦扩容到64KB以上,GC很难及时回收——尤其当协程里混用new StdClass()和array_merge_recursive()这类隐式分配堆内存的操作。
- 用
swoole_set_process_name("hyperf-worker")后,通过cat /proc/$(pgrep hyperf-worker)/status | grep VmRSS实时盯住单个Worker内存 - 主动触发GC:
gc_enable(); gc_collect_cycles();放在onWorkerStart回调里,或在max_request倒数第10次时强制执行 - 关键点:不要依赖PHP自动GC,协程场景下
gc_collect_cycles()需在协程退出前显式调用,否则引用计数可能永远不归零











