worker进程rss持续上涨但非真实泄漏,大概率是内存碎片化导致的“假泄露”;需先排除静态缓存、协程残留等真泄漏,再通过ps aux观察rss分布、/proc/pid/smaps分析分配器缓存、启用jemalloc或定期重启worker来缓解。

Worker 进程长期运行后物理内存(RSS)持续上涨、重启回落,但实际并无真实泄漏——这大概率是内存碎片化引发的“假泄露”,而非代码级内存泄漏。关键在于区分 真泄漏(对象被意外持有无法回收)和 假泄漏(内存分配器未归还、空闲块不连续导致 RSS 居高不下)。
确认是否为碎片化假象
先排除真实泄漏,再聚焦碎片问题:
- 用
ps aux --sort=-%mem | head -5查看各 Worker 进程 RSS 是否均匀上涨;若仅个别 Worker 异常飙升,优先查逻辑泄漏(如静态缓存、协程上下文残留) - 观察
memory_get_usage(true)与memory_get_peak_usage(true):若两者差距小、且增长平缓,而 RSS 持续冲高,说明 PHP 堆内分配不多,问题在底层分配器(如 ptmalloc)或扩展层 - 检查
/proc/[pid]/smaps中MMAP_AREA和Heap区域的mmapped大小与Rss差值——若 Rss 远大于 mmapped + heap 分配量,说明大量内存被保留在分配器缓存中未释放
针对性缓解内存碎片
PHP Worker 场景下,碎片主要来自频繁 malloc/free 小块内存(如字符串拼接、JSON 解析、临时数组),ptmalloc 默认行为会保留空闲 chunk 不还给内核:
- 在 Worker 启动时(
onWorkerStart回调内)调用gc_disable()并设置ini_set('opcache.enable', '0')(若非必需),减少 PHP 层额外内存抖动 - 主动触发分配器整理:在低峰期调用
mallctl("arena.0.purge", null, null, null, 0)(需编译时启用 jemalloc 并暴露 mallctl);或改用 jemalloc 替代默认 ptmalloc,它对碎片更友好 - 避免高频分配不定长小内存:将短生命周期字符串转为固定长度 buffer 复用;JSON 解析前预估大小并复用
json_decode($str, false, 512, JSON_BIGINT_AS_STRING)的深度限制,防栈/堆过度扩张
监控与验证手段
不能只看 RSS,要结合多维度指标交叉判断:
- 用
cat /proc/[pid]/status | grep -E "VmRSS|VmSize|VmData"对比 RSS 与 Data 段增长趋势;若 VmData 稳定而 VmRSS 持续涨,指向分配器缓存膨胀 - 开启
php-memprof(务必在每个子进程onWorkerStart后启用),生成 callgrind 文件,用 KCacheGrind 查看emalloc调用是否集中在某几个函数——若分散且无明显泄漏点,支持碎片判断 - 定期执行
echo 1 > /proc/sys/vm/compact_memory(需 root)手动触发内核内存整理,观察 RSS 是否短暂下降;若有效,说明外部碎片确实存在
长期运行下的工程实践
碎片无法根治,但可控制在合理范围:
- 设置 Worker 生命周期:例如每处理 5000 个请求或运行 4 小时自动退出,由主进程拉起新 Worker,天然重置分配器状态
- 禁用
opcache.memory_consumption过大配置(如 >128M),避免 OPCache 共享内存段加剧地址空间碎片 - 对高内存消耗模块(如 Excel 导出、大文件解析)做进程隔离:用
proc_open启独立短期子进程处理,完成后彻底释放全部资源











