ThinkPHP 6.0在PHP-FPM下内存“缓慢上涨、重启回落”多数非真正泄漏,而是配置或设计问题;需先用memory_get_usage(true)埋点验证单请求内是否不释放,再排查Runtime缓存失效、Collection引用持有及pm.max_requests等配置项。

ThinkPHP 6.0 在 PHP-FPM 模式下出现内存“缓慢上涨、重启回落”现象,多数情况并非真正泄漏,而是运行机制与配置不匹配导致的资源滞留。关键要区分:是单次请求内内存不释放(代码级问题),还是多请求间内存持续累积(配置或设计问题)。
先确认是不是真泄漏
别急着改代码,先用 PHP 原生函数验证:
- 在控制器入口、数据库查询后、循环结束前各加一行:
error_log('mem: ' . memory_get_usage(true), 3, '/tmp/tp6_mem.log'); -
memory_get_usage(true)查的是真实分配的内存页,比默认false更准 - 观察单个请求生命周期内内存是否只增不减——若增长后不回落,才是代码层需干预;若每次请求都从低点开始,则不是泄漏,可能是 FPM 配置或缓存策略问题
重点排查 Runtime 缓存失效
ThinkPHP 的 Runtime 目录若被频繁清空,会触发大量文件 I/O 和重复编译,间接推高内存并干扰 OPcache:
- 检查各模块的 BaseController 是否含类似
delRuntime()或手动清空缓存逻辑 - 对比正常模块和异常模块的 Runtime 目录:若一个模块缓存文件不断生成又消失,说明有逻辑在每次请求时强制刷新缓存
- 特别注意配置加载失败场景(如
C('not_first')返回 null),会导致框架反复尝试初始化,连带清空 Runtime
ORM 查询必须及时“断开引用”
TP6.0 的查询方式对内存影响差异明显,尤其在分页或批量处理时:
立即学习“PHP免费学习笔记(深入)”;
- 避免直接使用
Db::table()->select(),它返回Collection对象,自带迭代器和查询上下文,易被闭包或静态变量意外持有 - 大数据量优先用原生 SQL:
Db::query("SELECT * FROM table LIMIT ?, ?", [$offset, $limit]) - 若必须用 ORM,查完立刻转数组并销毁对象:
$data = Db::table('log')->select()->toArray(); unset($data); - 切勿在全局中间件或事件监听器中缓存
Query实例——TP6 查询构建器不是无状态的
检查 php-fpm 关键配置项
看似无关的配置,可能放大内存滞留效应:
-
pm.max_requests设置过高(如 102400)会让进程长期服役,小泄漏日积月累成显性问题;建议设为 500–2000,让子进程定期重启释放资源 -
opcache.enable开启但opcache.revalidate_freq过低(如 0),会导致 OPcache 频繁失效,重新解析 PHP 文件,加重内存压力 - 关闭 OPcache 后内存恢复正常?大概率是缓存策略与文件变动不匹配,而非代码本身泄漏



















