如何通过jstack与jmap分析一次线上故障

发布时间:2021-12-13 21:25:22 作者:柒染
来源:亿速云 阅读:255
# 如何通过jstack与jmap分析一次线上故障

## 前言

在Java应用的线上运维中,突发性能问题或内存泄漏是常见挑战。掌握`jstack`和`jmap`这两个JDK工具的组合使用,能够快速定位线程阻塞、死锁、CPU飙高及内存异常等问题。本文将通过一个真实案例,演示如何通过这两个工具进行系统化分析。

---

## 一、问题现象

某电商平台订单服务突然出现以下异常:
- 接口平均响应时间从50ms飙升到2000ms
- CPU利用率持续超过90%
- 频繁触发Full GC,Old区内存无法释放
- 监控系统显示线程池满载告警

---

## 二、工具准备

### 1. jstack核心功能
```bash
# 抓取线程快照(需Java进程PID)
jstack -l <pid> > thread_dump.log

# 带JVM内部信息输出
jstack -m <pid> > mixed_dump.log

2. jmap关键操作

# 生成堆内存直方图
jmap -histo <pid> > heap_histo.log

# 生成堆转储文件(生产环境慎用)
jmap -dump:live,format=b,file=heap.hprof <pid>

注意:执行jmap可能会引发STW,建议在低峰期操作并做好回滚准备


三、分析实战

阶段1:线程状态分析

通过jstack获取3次间隔10秒的快照:

for i in {1..3}; do jstack -l $PID > thread_$i.log; sleep 10; done

使用grep统计线程状态分布:

cat thread_*.log | grep java.lang.Thread.State | sort | uniq -c

输出显示:

 45   java.lang.Thread.State: RUNNABLE
 12   java.lang.Thread.State: BLOCKED
 3    java.lang.Thread.State: WTING

关键发现: - 大量线程卡在com.example.OrderService.processPayment()方法 - 12个线程阻塞在java.util.concurrent.locks.ReentrantLock$NonfairSync.lock()

阶段2:死锁检测

使用jstack自动检测:

jstack -l $PID | grep -A 10 "deadlock"

输出明确提示:

Found one Java-level deadlock:
  Thread 0x00007f8934017800 waiting for ReentrantLock@0x6d28f6d1
  Thread 0x00007f8934038800 holding ReentrantLock@0x6d28f6d1

阶段3:内存分析

生成内存直方图:

jmap -histo:live $PID | head -20

发现异常:

 num     instances         bytes  class name
----------------------------------------------
   1:       120452      27481024  [B  # 字节数组
   2:        98234      18000000  com.example.CacheEntry
   3:        78451      11200000  java.util.concurrent.ConcurrentHashMap$Node

内存问题: - CacheEntry对象异常堆积(本应不超过1万实例) - 配合MAT分析发现是本地缓存未设置TTL


四、根因定位

复合型问题:

  1. 线程死锁:支付服务与库存服务使用相同的锁顺序
  2. 内存泄漏:本地缓存实现类未清理过期数据
  3. 连锁反应:线程阻塞导致请求堆积,进而引发更多对象驻留内存

五、解决方案

1. 紧急恢复

// 修改锁获取顺序(遵循全局统一顺序)
public void processPayment() {
    synchronized(inventoryLock) {
        synchronized(paymentLock) {
            // 业务逻辑
        }
    }
}

2. 内存优化

// 改用Guava Cache并设置失效策略
Cache<String, Object> cache = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build();

3. 预防措施


六、分析技巧总结

jstack高效用法

  1. 时间序列分析:至少采集3次间隔快照
  2. 状态过滤:重点关注BLOCKED/WTING线程
  3. 锁关联:用grep -A 20 "held locks"追踪锁持有链

jmap最佳实践

  1. 安全采样:先用-histo确认是否需要-dump
  2. MAT联动:结合Eclipse Memory Analyzer分析引用链
  3. 基线对比:故障前后各保存一份堆快照

七、延伸思考

1. 为什么不能只依赖APM工具?

2. 容器化环境注意事项

3. 自动化分析方案

# 示例:自动分析线程dump的脚本
import re
with open('thread_dump.log') as f:
    content = f.read()
    blocked = len(re.findall('BLOCKED', content))
    print(f"阻塞线程比例:{blocked/total_threads:.1%}")

结语

通过jstack+jmap的组合拳,我们不仅解决了本次复合型故障,更建立了以下认知: 1. 线程问题往往表现为CPU高但实际是等待状态 2. 内存问题需要结合多个时间点数据对比 3. 预防性分析比应急处理更重要

建议每月定期进行故障演练,将工具使用转化为团队肌肉记忆。 “`

推荐阅读:
  1. 线上故障处理原则
  2. jvm监控工具jps,jstat,jstack,jmap的使用方法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jmap jstack

上一篇:如何进行JSON Web Token 入门

下一篇:circom/snarkjs实战zk rollup的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》