eventdispatcher监听器会堆积:frankenphp常驻进程导致$listeners和$sorted不自动清空,反复addlistener且未removelistener或使用闭包监听器易引发内存泄漏。

先看 EventDispatcher 和监听器是否堆积
FrankenPHP 是常驻进程,EventDispatcher 的 $listeners 和 $sorted 数组不会随请求结束自动清空。如果代码里反复调用 addListener()(比如在控制器或命令中动态注册),而没配对调用 removeListener(),监听器条目会越积越多。
更危险的是闭包监听器:哪怕调用了 removeListener(),只要闭包捕获了 $em、$container 或大数组,PHP GC 就无法回收——闭包对象本身还活着,捕获的变量就被强引用锁死。
- 检查点:
bin/console debug:event-dispatcher看监听器数量是否异常增长 - 临时加日志:在
addListener()前打点,确认调用频次和上下文 - 优先改用类方法注册:
[$this, 'onUserCreated']比function () use ($bigData) { ... }安全得多
查 Doctrine EntityManager 一级缓存是否未 clear()
Symfony 默认不自动 clear() EntityManager,尤其在 FrankenPHP 的长生命周期里,实体持续堆积在一级缓存中,$em->find() 越多,内存占用越不可控。
现象是:压测几轮后,memory_get_usage(true) 持续上涨,且 gc_collect_cycles() 无效——因为实体仍被 $em 强引用着。
- 必须在每个请求结束前手动调用:
$this->em->clear()(不是close()) - 若使用自定义中间件或内核事件(如
kernel.terminate),确保它在 FrankenPHP 的 worker 生命周期内可靠触发 - 禁用二级缓存:测试/生产环境配置
doctrine.orm.default_entity_manager.cache.driver: null
盯住 Symfony 的服务容器是否重复实例化
FrankenPHP 启动时加载一次 DI 容器,但某些配置会导致服务“每次请求都新建”——比如把 public: true 且带构造参数的服务设为 autowire: true,又没显式声明 shared: true,就可能意外创建多个实例。
典型高内存服务:HttpClient、CachePool、自定义的 LargeDataProcessor 类。它们一旦被多次实例化,内存几乎不释放。
- 运行:
bin/console debug:container --show-private,按内存预期排序看哪些服务实例数异常 - 重点检查
services.yaml中是否漏写shared: false(仅当真需要非共享时才设) - 避免在
__construct()中做重量级初始化;改用懒加载或reset()方法
确认 var-dumper 的 refCount 是否暴露循环引用
FrankenPHP 下 dump 大对象(如整个 User 实体树)后内存不降,不一定是泄漏,但 symfony/var-dumper 的 refCount 是关键线索:它虽不是实时值,却是克隆快照中唯一能直接指向循环引用的信号。
当某个对象的 refCount > 1 且类型为 Stub::TYPE_OBJECT,基本可断定它被多个父节点持有——比如同时被 $em、$cache 和一个未清理的闭包监听器引用。
- 必须调用:
$cloner->cloneVar($var)才能触发完整计数逻辑;直接dump($var)不行 - 在疑似泄漏点前后各采一次快照,对比
refCount变化趋势 - 注意排除
__sleep()干扰:该钩子可能绕过克隆路径,导致refCount显示偏低
EntityManager 缓存跨请求累积、以及服务容器配置里那个没写的 shared: true。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











