FrankenPHP 请求后内存未立即下降属正常现象,因PHP内存分配器不主动归还空闲页给OS,而是复用以提升性能;真实使用量(false)下降但真实分配量(true)不变,RSS监控易误判;需关注持续线性上涨或OOM。

FrankenPHP 请求结束后内存没有马上下降,是正常现象,不是泄漏——PHP 的内存管理机制决定了它不会立即将释放的内存归还给操作系统,尤其在常驻进程场景下。
PHP 的内存分配器(如 glibc malloc)默认不主动归还空闲页
PHP 进程使用的内存由底层 C 运行时管理。即使你调用了 unset()、$pdo = null 或 gc_collect_cycles(),已分配的内存块通常仍保留在进程堆中,供后续请求复用。这是性能优化策略,避免频繁系统调用 brk() / mmap()。
- 表现:
memory_get_usage(true)(真实分配量)可能不变,但memory_get_usage(false)(脚本使用量)会下降 - 验证方式:在请求末尾加
error_log('used: ' . memory_get_usage(false) . ', real: ' . memory_get_usage(true));,对比两者差值 - 影响:监控工具(如
ps auxRSS)看到的内存“不降”,容易误判为泄漏;实际可用内存仍在 PHP 堆内
Worker 模式下 GC 触发时机与效果有限
gc_collect_cycles() 只回收“已不可达”的对象,但无法强制操作系统回收物理内存。更关键的是:如果存在隐式强引用(如闭包捕获大数组、事件监听器未移除、日志 handler 缓存未 flush),GC 就收不回任何东西。
- 常见漏点:
Monolog\Handler\StreamHandler在长周期中累积未写入日志行;Doctrine\ORM\EntityManager一级缓存未clear() - 必须配对操作:GC 前先显式切断引用(如
$em->clear()、DB::purge()、fclose()),否则 GC 无对象可收 - 不要依赖
register_shutdown_function():Worker 生命周期中它不保证执行
OPcache 和框架缓存会持续占用内存且不释放
Worker 模式的核心优势就是复用 OPcache 和 Symfony/Laravel 编译后的容器,这些内存从进程启动起就常驻,不会随单次请求结束而释放——这正是你要的效果,不是 bug。
立即学习“PHP免费学习笔记(深入)”;
- 检查点:访问
/frankenphp/phpinfo,确认opcache.memory_usage.used_memory稳定在高位(如 ≥60%),说明复用成功 - 风险点:若
opcache.validate_timestamps = 1且文件挂载方式导致 stat 失败,OPcache 会退化为每次重编译,内存反而阶梯上涨 - Symfony 用户注意:
var/cache/prod/App_KernelProdContainer.xml文件修改时间应在 worker 启动后冻结,否则说明容器重建
真正要警惕的不是“内存没下降”,而是“内存持续线性上涨”或“worker 进程在 max_requests 之前就 OOM”。前者说明有引用未断,后者说明 GC 或清理逻辑失效——这两者才需要立即干预。



















