应先用pstack查看线程栈定位卡死或异常循环,再通过pmap -x与/proc/PID/smaps找出高RSS匿名内存段,最后用gdb dump并strings分析内容特征,结合错误日志和配置验证泄漏源。

当 Nginx 出现内存持续增长、worker 进程频繁被 OOM killer 杀掉时,仅靠 top 或 ps 看 RSS 值无法定位根源。pstack 和 gdb 不是用来直接“查内存泄露”的工具,但它们能快速暴露异常线程行为与可疑内存区域,为后续精准 dump 和分析指明方向。
pstack 快速识别卡死或忙等线程
pstack 本质是调用 gdb 打印所有线程当前调用栈,对排查“为什么内存不释放”非常关键:
- 运行 pstack PID(如
pstack $(pgrep nginx | head -n1)),重点看每个 Thread 的顶层函数 - 若大量线程停在
__lll_lock_wait、pthread_mutex_lock或自旋循环(如while(1) { ... })中,说明可能因锁竞争或逻辑缺陷导致资源未及时释放 - 若多个线程长期阻塞在
epoll_wait、nanosleep、read等系统调用上,属正常等待;但若伴随高 RSS,则需结合 pmap 查看是否在等待期间缓存了大量未清理数据 - 注意栈底是否反复出现同一业务模块(如某个第三方 Nginx 模块的 handler 函数),这往往是泄漏源头的强线索
用 pmap + smaps 锁定可疑大内存块
pstack 给出“哪里卡”,pmap 和 smaps 则指出“哪块内存大”:
- 先执行 pmap -x PID | sort -k3 -r | head -n5,找出 RSS 最大的几段匿名内存(标记为
[anon]) - 取其中地址范围(如
00007f2340539000-00007f235d553000),再用 cat /proc/PID/smaps | grep -A5 "7f2340539000" 查看该段的详细属性(尤其是Size、RSS、MMUPageSize) - 若某段
RSS达数百 MB 且MMUPageSize为 4kB(非大页),大概率是堆内存堆积;若为 2MB,则可能是 jemalloc/mimalloc 的大块分配,也需关注
用 gdb dump 内存并初步分析内容
锁定可疑地址后,用 gdb 抓取原始内存快照,人工判断数据性质:
- 执行 gdb -p PID,进入交互模式后输入:
dump binary memory ./mem_dump.bin 0x7f2340539000 0x7f235d553000
(注意起止地址必须十六进制,且与 smaps 中一致) - 退出 gdb 后,用 strings mem_dump.bin | head -n 50 查看可读字符串——常见泄漏特征包括:重复出现的 URL 路径、JSON 片段、SQL 查询模板、base64 编码的二进制 blob
- 若看到大量相似结构(如固定前缀+递增 ID),基本可断定是某类对象未被回收;若全是乱码但长度规整(如每块 8192 字节),可能是缓冲区池未复用
关联日志与模块配置交叉验证
技术手段给出线索,最终确认需结合上下文:
- 检查 /var/log/nginx/error.log 中是否在内存上涨时段集中出现特定模块报错(如 lua-resty-*、nginx-module-vts 的 warn 级日志)
- 确认是否启用了高风险配置:
lua_code_cache off(导致 Lua 字节码反复加载)、proxy_buffering off(关闭缓冲后响应体直写 socket,易在慢客户端场景堆积) - 对比正常 worker 与异常 worker 的启动时间:
ps -o pid,lstart,cmd -p PID;若异常进程运行时间明显更长,且内存随运行时间线性增长,泄漏概率极高


















