死循环会秒级耗尽内存并触发oom,memory_limit对其无效;需用ps/top监控rss、usr2/gdb抓栈、strace追踪内存分配来定位。

死循环直接耗尽内存,memory_limit根本拦不住
PHP Webman 里一旦出现真死循环(比如递归没出口、while(true)里忘 break、事件监听器互相触发),进程不会等你调用 gc_collect_cycles(),而是几秒内把所有可用内存填满,然后被系统 OOM Killer 杀掉,或者报 Fatal error: Allowed memory size of XXX bytes exhausted。这不是“内存泄漏”,是“内存抢劫”——它不靠引用累积,靠 CPU 疯狂分配新 zval。
关键区别:内存泄漏是缓慢爬升、重启后回落;死循环是垂直拉升、几秒崩盘。别用 memory_get_usage() 慢悠悠打点,得看 ps aux --sort=-%mem | head -5 或 top -p $(pgrep -f "webman start") 实时盯 RSS 值是否在秒级跳涨。
- 检查所有
while (true)、for (;;)块,确认有明确退出条件或usleep()降频 - 禁用无保护的递归调用:比如模型关联自动加载 + 全局事件监听器触发自身,形成
User::find()->orders->user->orders...链 - Webman 的
onRequest、onWorkerStart、定时器回调里,避免直接调用未加深度限制的递归函数 - 用
set_time_limit(30)在入口强制兜底,哪怕超时也比爆内存强
ini_set('memory_limit') 对死循环完全无效
死循环发生时,ini_set('memory_limit', '2G') 不起作用——因为 PHP 内存管理器在分配失败前就已崩溃,ini_set() 是运行时函数,执行前提是有内存可执行。更糟的是,在 CLI 模式下(Webman 默认)memory_limit 默认为 -1(无限制),意味着死循环会一路吃到系统内存见底,触发 OOM。
- CLI 启动必须显式加
-d memory_limit=512M:如php -d memory_limit=512M start.php start -d - 这个值不是越高越好:设成
-1或2G只会让 OOM 更晚发生,但进程更难被监控和回收 -
php_admin_value[memory_limit]在 PHP-FPM 下才生效,Webman 不走 FPM,该配置对它无意义 - 别信“先调大再排查”的说法——死循环下,调大只是延长灾难时间
定位死循环比修复更重要:用信号中断+堆栈抓取
Webman 进程卡死时,Ctrl+C 常常没响应,因为主循环占满 CPU。此时不能只看日志,要主动抓当前执行位置。
- 用
kill -USR2 {pid}(Webman 支持该信号)触发堆栈打印到runtime/log/worker.log - 若 USR2 无效,改用
gdb -p {pid} -ex "thread apply all bt" -ex "quit"抓全栈(需服务器装 gdb) - 重点搜
call_user_func、__invoke、trigger_error、event_emit等高频调用点,它们常是循环入口 - 临时在
app/Listener/和app/Middleware/所有 handle 方法开头加if (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'] > 5) { throw new \Exception('timeout in listener'); }快速暴露慢监听器
Webman 特有的死循环高危区:定时器 + 事件 + 中间件组合
Webman 的长驻特性放大了某些写法的风险:一个本该单次执行的逻辑,因监听器注册方式或定时器回调闭包捕获,变成无限自触发。
- 在
onWorkerStart里用Timer::tick()调用$app->make(SomeService::class)->doWork(),而doWork()内部又 emit 了会被同一监听器消费的事件 - 中间件中用
use ($request)捕获整个 Request 实例,再传给异步任务,任务里又调用$request->input()触发解析,而解析逻辑又依赖中间件已注册的全局钩子 - 自定义
ConnectionPool类里,get()方法没加最大重试次数,连接失败时反复 new 自身实例 - 路由组嵌套过深(如 5 层 group),匹配时正则回溯爆炸,表现为 CPU 100% + 内存缓涨(非严格死循环,但效果类似)
死循环问题最棘手的不是修复,是它往往藏在跨层调用链里——上层以为只是个普通回调,底层却悄悄形成了闭环。上线前务必用 strace -p {pid} -e trace=brk,mmap,munmap 看内存分配是否密集突增,这是比日志更早的预警信号。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











