jstack可快速定位死锁,关注“Found 1 deadlock”及线程持锁/等待锁关系;jmap用于内存分析,结合-histo与-dump交叉验证线程阻塞与对象泄漏;应按jstack→jmap -histo→jmap -dump顺序及时采集带时间戳的快照。

用 jstack 快速定位死锁线程
死锁发生时,Java 应用通常表现为部分请求卡住、响应变慢,但 CPU 占用未必高。此时执行 jstack
例如,若看到两个线程分别持有 LockA 等待 LockB,另一个持有 LockB 等待 LockA,基本可确认是经典循环等待型死锁。注意:jstack 需在应用仍在运行(未假死)时及时抓取,否则可能错过现场。
用 jmap 定位内存异常增长源头
当怀疑内存溢出(如 OOM-Heap Space)时,先用 jmap -heap HashMap$Node、自定义缓存对象、未关闭的连接池对象等)。
若需进一步分析,可用 jmap -dump:format=b,file=heap.hprof
将 jstack 与 jmap 结果交叉验证
单看线程或内存快照都容易误判。真正高效的方式是联合分析:
- 从 jstack 中标记出长期处于
WAITING或BLOCKED的线程,记录其线程 ID(nid)和堆栈中的关键类名; - 在 jmap -histo 输出中搜索这些类名,看对应实例数是否异常偏高;
- 若某业务线程卡在数据库连接获取上(jstack 显示阻塞在
DataSource.getConnection()),同时 jmap 显示大量PooledConnection或Statement实例未释放,大概率是连接未 close 导致池耗尽; - 若多个线程都在等待同一个锁对象(jstack 中 lock id 如
0x00000000c0a1b2c3),可在 jmap dump 文件中搜索该地址(MAT 中用 OQL 查询select * from java.lang.Object where @objectid == "0x00000000c0a1b2c3"),反查是谁创建并长期持有该锁。
实战建议:把快照采集变成标准动作
不要等到服务完全不可用才动手。建议在监控告警触发(如 GC 时间突增、线程数超阈值、HTTP 超时率上升)后立即执行:
- 顺序很重要:先 jstack(秒级完成,无侵入),再 jmap -histo(毫秒级),最后考虑 jmap -dump(可能暂停应用几秒,慎用于生产高峰);
-
保留时间戳:给每个快照文件命名带上时间,如
jstack_20240520_1423.pid,方便回溯; - 避免重复 dump:jmap -dump 会触发 Full GC,若已在频繁 GC,可能加剧问题,优先用 -histo 初筛;
- 本地复现 + 代码审计:拿到快照线索后,结合调用栈中的方法名、行号,在代码中定位同步块、锁粒度、资源释放逻辑,比纯靠工具更可靠。
不复杂但容易忽略











