jstack本身不显示堆内存对象,但能通过识别BLOCKED、WAITING/TIMED_WAITING、可疑RUNNABLE线程及其调用栈(如静态集合操作、未关闭资源、死锁),定位间接导致对象无法被GC回收的根源;需结合jstat验证老年代增长与FGC异常,并辅以jmap -histo:live交叉分析。

Java 生产环境出现内存泄漏时,jstack 本身不直接显示堆内存对象,但它能帮你揪出“谁在拖住内存不放”——关键在于识别那些异常存活、持续持有引用或阻塞资源的线程。重点不是看堆,而是看线程行为如何间接导致对象无法被 GC 回收。
快速获取线程快照并保存分析
先稳定抓取现场数据,避免瞬态信息丢失:
- 用
jps -l或ps -ef | grep java找到目标 Java 进程 PID - 执行:
jstack -l <pid> > thread_dump_$(date +%s).log(加-l可输出锁信息,对排查死锁和同步阻塞至关重要) - 建议连续采集 3–5 次,间隔 10–30 秒,便于比对线程生命周期变化
重点关注这三类高风险线程状态
不要只扫一眼“RUNNABLE”,要结合堆栈内容判断行为实质:
-
BLOCKED:多个线程卡在同一个
waiting to lock <0x...>,说明临界区过长或锁粒度太粗,请求堆积 → 缓存/队列对象持续增长 → 内存上涨 -
WAITING / TIMED_WAITING:线程停在
Object.wait()、Thread.join()或LockSupport.park(),若无超时或唤醒逻辑,可能长期持有所在对象(如监听器、回调上下文)不释放 -
RUNNABLE 但调用链可疑:例如反复调用
list.add()、map.put()、StringBuilder.append(),尤其出现在静态集合、缓存类或单例中,极可能是泄露源头
从堆栈中识别典型泄露模式
打开 thread_dump.log,搜索这些信号词:
立即学习“Java免费学习笔记(深入)”;
- 线程名含
Cache、Worker、Handler、Leak等业务标识,且数量随时间增多 → 检查是否未复用或未 shutdown 线程池 - 堆栈中频繁出现自定义类 +
static字段(如MyService.cache),配合add/put调用 → 静态集合不断累积对象 - 出现
java.io、javax.sql、org.apache.http相关类,且处于 RUNNABLE 或 WAITING 状态 → 检查流、连接、客户端是否被正确关闭或复用 - 发现
Found one Java-level deadlock提示 → 死锁线程会一直持有对象引用,直接阻断 GC
结合 jstat 验证线索是否成立
jstack 是“线程视角”,需搭配内存指标交叉验证:
- 执行
jstat -gc <pid> 5000(每 5 秒刷新),观察OU(老年代使用量)是否持续上升、FGC(Full GC 次数)是否激增但OU不降 - 若 jstack 发现大量 WAITING 线程,而 jstat 显示老年代缓慢上涨 → 很可能这些线程持有的监听器、缓存项、内部类实例正把外部对象钉在堆里
- 此时可补一个
jmap -histo:live <pid>查看高频存活对象,再回溯到 jstack 中对应线程的调用路径


















