worker模式下static和$globals跨请求残留,需手动清理;pdo连接、ob缓存、监听器、大对象及容器单例均须显式释放,否则引发内存泄漏或脏读。

worker 模式下 static 和 $GLOBALS 不会自动重置
FrankenPHP 在 worker 模式中复用 PHP 进程,static 属性和 $GLOBALS 里的值会跨请求残留。比如 static $user_id = null; 在第 50 个请求里可能还存着上一个用户的 ID —— 这不是 bug,是设计使然,但必须你来清理。
常见踩坑点:
- 在 preload 阶段执行
$GLOBALS['config'] = loadConfig();,导致所有后续请求共享同一份配置引用 - 把临时缓存键、用户 token、数据库连接句柄塞进
static,没重置就直接复用 - 用
var_dump(array_keys($GLOBALS));在请求末尾快速检查有没有意外挂载的变量
解决办法:所有 static 声明都要问一句“它真需要跨请求保留吗?”否则改用函数参数、php.Context 或依赖注入传值。
数据库连接和 ORM 缓存必须显式释放
PDO 实例和 Doctrine/Laravel 的实体管理器不会随请求结束而销毁,它们持有的连接、一级缓存、未提交事务都会累积,最终引发内存泄漏、脏读或连接数爆满。
实操建议:
- Laravel:在请求结束前调用
DB::disconnect()(非持久连接),或$db->reconnect()(持久连接异常后) - Symfony + Doctrine:每个事务块结尾加
$entityManager->clear(),批量操作后尤其关键 - 禁用
PDO::ATTR_PERSISTENT,除非你有完整连接池逻辑;否则直接设为false - 用
netstat -an | grep :3306观察连接数是否随请求数线性上涨,上涨即说明没释放
ob_start() 和事件监听器不能靠“自动结束”
ob_start() 本身不跨请求,但如果你在 preload 阶段误调用,或在回调里漏掉 ob_end_flush() / ob_end_clean(),会导致后续响应头错乱、内容重复甚至 fatal error。
监听器也一样:闭包形式的监听器如 function () use ($bigObject) { } 会强持有大对象,即使调用 removeListener() 也无法释放。
检查与修复:
- 入口处检查
ob_get_level()—— 应为0;若 > 0,说明上个请求没清理干净 - 所有
ob_start()必须在请求处理逻辑内调用,且严格配对ob_end_flush()或ob_end_clean() - 注册监听器优先用
[$this, 'method']或static function,避免闭包捕获容器或实体
手动触发 GC 并清空已知大数组
PHP 的引用计数 GC 能处理大部分情况,但循环引用或长时间运行的 worker 容易堆积不可达对象。别等脚本退出才清理 —— 请求结束前就得动手。
关键动作:
- 在
kernel.terminate或等效钩子中调用gc_collect_cycles() - 主动清空已知大数组:如临时结果集、日志缓冲区、解析后的配置数组,直接赋值为
[]或null - 对
Ds\Set、Ds\Map等结构,调用$set->clear()而非仅 unset 变量 - 避免只靠
unset($var)就以为万事大吉 —— 它只是断开变量引用,对象本身是否释放取决于引用计数是否归零
最易被忽略的是:服务容器里已解析的单例实例,框架通常不自动重置,得手动清空或重建容器实例。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











