composer install 后常驻服务变慢的根源是依赖库中未释放资源,而非 composer 本身泄漏;典型场景包括事件循环、协程组件中对象未 unset、静态单例累积、闭包捕获大对象导致引用锁死,以及 autoload 时静态初始化引发的内存持续增长。

为什么 composer install 后的常驻服务越跑越慢?
不是 Composer 本身泄漏,而是它加载的依赖(尤其是事件驱动、协程或长生命周期组件)在反复调用中未释放资源。常见于用 symfony/console + reactphp/event-loop 或 swoole + monolog 的 CLI 常驻进程——每次处理请求都 new 对象但没 unset,GC 又不触发,内存就稳涨。
排查第一原则:别猜,先看真实增长点。用 memory_get_usage(true) 在关键入口/循环前后打点,比看 top 更准。
- 加日志:
echo "mem: " . memory_get_usage(true) . "\n";记录每次任务开始前和结束后 - 避免只用
memory_get_usage()(返回当前脚本分配量),必须带true参数获取实际内存块大小 - 注意:CLI 模式下
gc_disable()是默认行为,得手动gc_enable()并定期gc_collect_cycles()
vendor/autoload.php 加载后全局变量怎么偷偷吃内存?
Composer 自动生成的 autoloader 本身不泄漏,但很多包会在 autoload.php 被 require 时执行静态初始化——比如 monolog 的 Logger::addRecord 默认注册全局 handler,doctrine/common 的 ClassLoader 缓存类映射,这些都在进程生命周期内累积。
检查方法很简单:启动服务后立即 dump 全局符号表,再跑几轮任务再 dump,对比差异:
var_dump(array_keys($GLOBALS));
重点关注:$GLOBALS['log']、$GLOBALS['container']、$GLOBALS['_composer_autoloaded_files'] 这类非 PHP 内置变量。
- 第三方包若用了
static $instance单例且没提供 reset 方法,就得自己在任务结束时手动清空(如Monolog\Logger::$instances = [];) - 禁用自动加载器缓存:在
composer.json中设"optimize-autoloader": false,避免生成vendor/composer/autoload_classmap.php这种全量静态数组 - 慎用
class_alias()—— 它注册的别名不会被 GC 回收,常驻进程里用一次就留一辈子
协程环境(Swoole/ReactPHP)下 use 闭包导致引用计数锁死
这是最隐蔽的泄漏点:闭包捕获了大对象(比如整个 $this 或 DB 连接),而协程切换时 PHP 不会自动销毁闭包作用域,引用计数始终 >0,对象永远不释放。
典型代码:
$server->on('request', function ($req, $resp) use ($app) {
$resp->end($app->handle($req)); // $app 持有容器、连接池、日志实例...
});
解决核心是「按需注入,用完即弃」:
- 把
$app换成工厂函数:use (fn() => app()),每次调用新建轻量上下文 - 避免在闭包里直接
use ($db),改用use ($dbFactory),并在闭包内调用$dbFactory->make()获取新实例 - Swoole 场景下,
go(function () { ... })内部的use闭包生命周期与协程绑定,但父协程若长期存在,仍可能拖住子闭包引用链
如何验证是不是 Composer 依赖本身的 bug?
别急着提 issue。先做最小化隔离:用 composer create-project --no-install 新建空白项目,仅 require 出问题的那个包,写最简常驻脚本跑 1000 次相同操作,观察内存曲线。
重点盯这些信号:
- 内存增长是否与调用次数线性相关(比如每轮 +2KB)?→ 很可能是对象未释放
- 增长是否在某次调用后突增且不再回落?→ 可能是静态缓存无限膨胀(查包里有没有
static $cache = [];且无清理逻辑) - 用
php -d memory_limit=-1 -r "print_r(get_defined_functions()['internal']);"看是否有可疑扩展函数(如某些旧版redis扩展的 pipeline 模式泄漏)
真正麻烦的不是泄漏本身,而是泄漏路径藏在 autoload 链深处——比如 A 包 require B 包,B 包的某个 trait 里有个 static 属性被 C 包的 event listener 无意间写了值,这种跨包隐式耦合,靠日志和内存快照比靠代码扫读更可靠。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











