内存泄漏需确认rss持续单向增长,重点排查php-cgi、python、node等长期进程;php-fpm要检查pm.max_children配置与实际rss匹配;supervisor/pm2日志中exited too quickly或频繁重启提示内存不足;代码级用memory_get_usage()前后对比可快速定位泄漏。

宝塔面板里网站程序内存泄漏,不能只盯着“内存占用高”看——得先确认是不是真泄漏,再锁定是哪个进程、哪段代码在吃内存。
怎么看进程 RSS 是否持续上涨
内存泄漏的核心特征是:某个进程的 RSS(常驻内存)随时间推移单向增长,且不因请求结束或空闲而回落。宝塔任务管理器能直接看到这个值,但默认按 CPU 排序,得手动切到内存页并点击 RSS 列标题降序排列。
- 重点关注运行时间超过 30 分钟、
RSS持续高于 300MB 的进程,尤其是名字含php-cgi、python、node或你自定义脚本名的条目 - 点进进程详情,看
启动时间和CMD字段:如果进程已跑几小时,RSS还在缓慢爬升,且命令行显示是长期驻留服务(如gunicorn、pm2 start app.js),泄漏嫌疑极高 - 别被
cached和buffers干扰——宝塔首页显示的“已用内存”包含这部分,不代表真被进程占满;真正要盯的是swap是否持续上升、uss(独占物理内存)是否同步涨
PHP-FPM 进程泄漏怎么快速验证
大量 php-fpm worker 占用过高内存,不一定是代码问题,更可能是配置没匹配真实负载。先用终端查真实内存占用,再比对配置是否合理:
- 执行
ps aux --sort=-%mem | grep php-fpm,看每个 worker 的RSS值(单位 KB),换算成 MB 后取中位数,比如普遍在 80–120MB,就不是“每个只占 20MB”那么乐观 - 检查
/www/server/php/81/etc/php-fpm.d/www.conf(版本号按你实际填)里的pm.max_children:若设为 100,但每个进程实占 100MB,那光 PHP 就要吃掉 10GB,远超 2GB 小内存 VPS 承载能力 -
pm = dynamic是必须项,static模式会恒定维持全部子进程,小内存机器极易被拖垮;同时确保pm.max_spare_servers ≤ pm.max_children,否则重启时直接报错ERROR: unable to set 'max_children' value
Supervisor 或 PM2 守护进程频繁重启的内存线索
Supervisor 和 PM2 本身不报内存错误,但它们的日志里藏着关键线索:进程启动后几秒就退出,大概率是内存不足或初始化失败。
- Supervisor 插件里点对应进程的
日志按钮,重点查.err.log文件,搜proc_open is not available或The Process class relies on proc_open——这是 PHP 禁用函数导致队列无法启动的典型表现 - PM2 配置里
max_memory_restart: '1G'只是兜底,不是根治;若该值频繁触发,说明进程内存确实在涨,得配合memory_get_usage()在代码里打点验证 - Supervisor 主日志路径是
/www/server/panel/plugin/supervisor/log/supervisord.log,里面出现exited too quickly或BACKOFF,基本可断定进程启动即崩,不是慢,是根本活不过 1 秒
PHP 脚本级泄漏检测的最小可行动作
不用装 Xdebug、不用改架构,两行代码就能初步判断某脚本是否存在内存累积:
- 在脚本最开头加:
echo 'Start: ' . memory_get_usage() . PHP_EOL; - 在业务逻辑结束后、响应输出前加:
echo 'End: ' . memory_get_usage() . PHP_EOL; - 反复刷新页面或用
curl请求多次,观察两次输出的差值是否逐次变大;如果每次增长 2MB 且不回落,基本就是变量未 unset、静态数组不断 push、或 PDOStatement 没 close 导致的泄漏 - 加一行
gc_collect_cycles();在末尾再测一次,如果回收后内存仍不降,说明有循环引用或对象被全局变量/静态属性长期持有
真正的难点不在发现泄漏,而在区分“缓存行为”和“泄漏行为”——比如 OpCache 加载大量文件后内存上升是正常的,但同一份代码重复执行 100 次后内存涨了 500MB,那就不是缓存,是没释放。










