Laravel内存泄漏主因是静态属性、事件监听器、模型关系等延长对象生命周期;需显式unset、禁用事件、改用DB::table插入、切换Session驱动并精准打点定位引用源。

Laravel 本身不主动管理 PHP 对象的垃圾回收,它依赖 PHP 的 GC 机制;但框架层大量使用静态属性、事件监听器、模型关系和查询日志,会人为延长对象生命周期,导致内存无法及时释放。
DB::transaction 中的模型实例不释放
在大事务循环里反复 new Model 或调用 create(),每个模型实例都携带原始属性、变更跟踪、关联缓存等元数据。PHP GC 不会立即回收它们,尤其当存在闭包引用或静态监听器时。
- 处理完一批记录后,必须显式
unset($model)或unset($collection),不能只靠作用域自动销毁 - 避免在事务内调用
$model->load('relation');如已加载,处理完立刻unset($model->relationName) - 若需批量写入,优先用
DB::table('xxx')->insert($rows)替代模型 save,跳过整个 Eloquent 生命周期
Session 文件驱动触发的 gc() CPU 尖峰
默认 file Session 驱动会在每次请求结束时按概率(默认 2%)执行 FileSessionHandler::gc(),扫描整个 storage/framework/sessions/ 目录。目录文件数超万后,该操作阻塞主线程并吃满 CPU。
- 生产环境务必切换为
redis或database驱动,redis由服务端自动淘汰过期 key,完全规避 PHP 层 GC - 如暂不能改驱动,可临时调高
session.lottery概率(如[1, 1000]),大幅降低 GC 触发频率 - 切勿手动在控制器里调用
session()->gc()—— 它不会生效,且可能干扰框架内部逻辑
模型事件与全局监听器长期驻留内存
Event::listen('eloquent.*', ...) 或模型中定义的 static::created() 等静态监听器,只要没显式移除,就会一直持有回调引用,连带捕获其作用域内所有变量。
- 大任务前用
Event::forget('eloquent.saved: App\Models\User')精确卸载,比Event::forget('eloquent.*')更安全 - 临时禁用某模型事件:设
$model->fireModelEvent = false,完成后恢复(注意不是静态属性) - 工厂类场景下,用
Factory::disableModelEvents()全局关闭,结束后再enableModelEvents()
调试时 memory_get_usage() 打点位置很关键
只看最终内存值没意义,必须在具体上下文打点才能定位泄漏源。比如在 DB::transaction() 块内外、循环体首尾、get() 调用前后分别测值。
- CLI 下用
memory_get_usage(true),它返回分配器实际申请的内存,含碎片,更贴近真实压力 - 避开 Blade 模板中打点 —— 渲染过程有延迟加载和缓存,数值失真
- 配合
debug_zval_dump($obj)查 refcount:若refcount > 2且is_ref === true,说明被静态数组、全局变量或未清除的闭包持有了
真正难处理的不是“怎么释放”,而是“谁还在引用它”。模型、事件、Session handler、查询日志 —— 这些 Laravel 默认启用的便利机制,恰恰是内存滞留的主因。不打断引用链,unset 和 gc_collect_cycles() 都只是隔靴搔痒。











