Composer 在 Swoole 常驻进程中误用 dump-autoload 或重复加载 autoload.php 会导致 RSS 持续上涨且 GC 不回落,根本原因是 ClassLoader 实例被静态属性强引用而无法销毁,正确做法是构建期生成优化 autoload 并仅一次性加载。

Composer 本身不泄漏,但在 Swoole 常驻进程中反复执行 dump-autoload 或重复加载 vendor/autoload.php,会直接导致 RSS 持续上涨且 GC 不回落——这不是 bug,是误用。
为什么 composer dump-autoload 在 Swoole 里“漏内存”
它不是运行时命令,而是构建期工具。每次执行都会 new 一个 Composer\Autoload\ClassLoader 实例,并注册到 spl_autoload_functions();旧实例因被静态属性(如 ClassMapGenerator::$classMap)强引用而无法销毁。
- 典型现象:
ps aux --sort=-rss显示 Worker 进程 RSS 每次调用后涨 10–50MB,gc_collect_cycles()后无变化 - 验证方式:启动后运行
php -r "print_r(spl_autoload_functions());",再执行一次composer dump-autoload,对比输出中ClassLoader对象 ID 数量是否增加 - 根本原因:Swoole 进程不退出,而
dump-autoload的设计前提是“执行完就退出”,两者生命周期错配
如何确认是不是 autoload.php 被重复加载
这是比插件或 SDK 更高频的泄漏源头。重复 require 会导致多个 autoloader 实例叠加注册,且每个都持有自己的 PSR-4 映射和 files 全量 include 缓存。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 在入口文件顶部加
echo "autoload loaded\n";,请求多次看是否重复输出 - 检查中间件、路由层、异常处理器中是否写了
require_once __DIR__.'/../vendor/autoload.php' - 若
autoload.files中引入了大体积helpers.php(含数百函数),每次加载都会include_once进内存且永不释放 - 禁用插件验证:
COMPOSER_NO_PLUGINS=1 composer dump-autoload,观察 RSS 增长是否消失
生产环境正确的 Composer 使用姿势
常驻进程里没有“热重载 autoload”这回事。所谓“动态更新类映射”,唯一安全的方式是重启 Worker,而不是 reload autoloader。
- CI 构建阶段执行:
composer dump-autoload -o --no-dev --classmap-authoritative,生成优化后的autoload_classmap.php并部署整个vendor/ - 入口文件(如
server.php)中只做一次require __DIR__.'/vendor/autoload.php';,之后绝不重复 require - 禁止在
onReceive/onRequest回调中调用任何composer命令或修改ClassLoader状态(如addPsr4()) - 若需上线后更新代码,用
swoole_reload或max_request=1000触发 Worker 自动重启,而非尝试“刷新 autoload”
容易被忽略的静态初始化泄漏点
很多包在 vendor/autoload.php 被加载时就执行静态初始化——比如 Monolog 注册全局 handler、Doctrine 缓存类映射、甚至某些 SDK 初始化连接池。这些操作只做一次,但效果持续整个进程生命周期。
- 检查
$GLOBALS差异:var_dump(array_keys($GLOBALS));启动前后对比,关注$GLOBALS['log']、$GLOBALS['container']等非内置变量 - 第三方 DI 容器若用
static $instances = []缓存服务,需手动清理(如Monolog\Logger::$instances = [];) - 慎用
class_alias():它注册的别名不会被 GC 回收,常驻进程里用一次就留一辈子 - 闭包捕获
$this或 DB 连接对象,在协程切换中极易锁死引用计数,xdebug_debug_zval('container')查is_ref和refcount可快速定位
真正难排查的从来不是“哪里泄漏”,而是“谁在悄悄持有着不该持有的引用”——尤其当它藏在静态属性、闭包 use、或扩展的 C 层 malloc 里时,memory_get_usage() 根本看不见,gc_collect_cycles() 也清不掉。


















