jstack本身不能直接定位内存溢出根源,但可通过分析RUNNABLE、BLOCKED、WAITING等异常线程状态,结合多时间点快照比对与jmap堆直方图交叉验证,精准识别持续创建对象或持有资源不释放的问题线程。

直接用 jstack 本身不能“看到”内存溢出的根源,但它能帮你定位那些正在悄悄吃掉内存、最终引发 OutOfMemoryError 的线程。关键不是看单次快照,而是结合状态、调用链和时间变化,找出异常线程行为。
先抓准目标线程:从高占用线索入手
内存溢出往往伴随线程异常堆积或持续运行。别一上来就翻完整日志,先缩小范围:
- 用
top -H -p <pid>或ps -mp <pid> -o THREAD,tid,time | sort -k2r找出 CPU 或运行时间最高的线程 ID(TID) - 把 TID 转成十六进制:
printf "%x\n" <tidenter> - 在
jstack -l <pid>输出里搜这个十六进制值:grep -A 10 "<hex-tid>" thread_dump.log,快速定位该线程的完整堆栈
重点盯住三类危险线程状态
不是所有线程都可疑,要聚焦在容易“卡住资源”或“不断创建对象”的状态:
-
RUNNABLE:表面在跑,但若堆栈里反复出现
StringBuilder.append、ArrayList.add或自定义缓存类(如CacheManager.loadBigData),说明它可能在无节制地生成大对象 -
BLOCKED:多个线程卡在同一把锁上(比如
-waiting to lock <0x...>),会导致任务积压、线程数飙升,间接耗尽栈空间或触发 GC 压力 -
WAITING / TIMED_WAITING:如果线程长期停在
Object.wait()、Thread.sleep()或数据库连接池获取处,且没设超时,可能造成线程无法释放,数量越积越多
比对多次快照,识别“不死线程”
单次 dump 很难判断是否异常,真正的线索藏在变化中:
- 每隔 30–60 秒执行一次:
jstack <pid> > dump_$(date +%s).log - 对比不同时间点的文件,关注:
- 同名线程(如
LeakThread-123)是否一直存在且堆栈未变?→ 可能卡死或无限循环 - 线程总数是否持续上升?→ 暗示线程池未复用、或 new Thread() 泄露
- 某类业务线程(如
BackgroundWorker)实例数是否翻倍增长?→ 很可能没正确 shutdown
- 同名线程(如
交叉验证:把线程栈和对象堆关联起来
光看线程不够,得确认它到底持有了什么:
- 用
jmap -histo <pid>查看哪些类实例暴涨(比如byte[]、String、自定义 DTO) - 回到
jstack日志,搜索这些类名或对应包名(如com.example.dto.User),看它们是否出现在 RUNNABLE 线程的调用栈顶层 - 若发现某线程反复调用
new User[10000]或往静态HashMap<String, List>里塞数据,基本就是泄露源头
















