jstack生成及分析线上问题

432 阅读3分钟

1. 生成 jstack 日志

  • 命令

    jstack <pid> > thread_dump.txt  # 直接生成线程转储
    # 或使用更现代的 jcmd 工具:
    jcmd <pid> Thread.print > thread_dump.txt
    

    <pid> 是 Java 进程的 ID,可通过 jps 或 ps -ef | grep java 查看。

  • 建议:在问题发生时多次生成日志(例如间隔 5-10 秒),便于对比分析。


2. 理解线程转储结构

一个典型的线程转储包含以下信息:

  • 线程状态:如 RUNNABLEBLOCKEDWAITINGTIMED_WAITING
  • 线程栈轨迹:显示线程当前执行的方法调用链。
  • 锁信息:线程持有的锁或等待的锁(如 0x000000076bf0f7d8)。
  • 守护线程/用户线程:标识线程类型。

3. 关键分析步骤

(1) 定位死锁

  • 搜索关键字:在日志中查找 deadlock 或 Found one Java-level deadlock

  • 查看死锁详情:死锁的线程会明确标识,并显示相互等待的锁资源。

  • 示例

    "Thread-1" #12 prio=5 os_prio=0 tid=0x00007f01340cc800 waiting for monitor entry [0x00007f00f1efd000]
       java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.DeadlockExample.methodB(DeadlockExample.java:25)
        - waiting to lock <0x000000076bf0f7d8> (a java.lang.Object)
        - locked <0x000000076bf0f7e8> (a java.lang.Object)
    

(2) 分析高 CPU 占用

  • 步骤

    1. 使用 top -H -p <pid> 或 pidstat -t -p <pid> 查找占用 CPU 高的线程 ID。
    2. 将线程 ID 转换为十六进制(如线程 ID 12345 → 0x3039)。
    3. 在 jstack 日志中搜索该十六进制 ID(nid=0x3039),查看线程的栈轨迹。
  • 常见原因

    • 循环未退出(如 while(true) 无休眠)。
    • 密集计算(如复杂算法、正则表达式)。
    • 锁竞争激烈(大量线程处于 BLOCKED 状态)。

(3) 检查线程阻塞(WAITING/BLOCKED)

  • 常见场景

    • WAITING (on object monitor):线程在等待 wait() 或 notify()
    • BLOCKED (on object monitor):线程在等待进入同步块或获取锁。
    • TIMED_WAITING (sleeping):线程调用了 sleep() 或 join(timeout)
  • 关注点

    • 大量线程处于 BLOCKED 状态可能表明锁竞争。
    • 长时间 WAITING 可能是资源未释放或任务调度问题。

(4) 识别资源等待

  • I/O 或网络操作:线程可能在等待数据库、HTTP 响应或文件读写。

    "http-nio-8080-exec-1" #20 daemon prio=5 os_prio=0 tid=0x00007f01340cc800 runnable [0x00007f00f1efd000]
       java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    
    • 如果大量线程卡在 socketRead,可能是下游服务响应慢。

(5) 检查线程池问题

  • 线程池中的线程若长期空闲或任务堆积,可能表明任务分配不均或队列过长。

  • 示例:

    "pool-1-thread-1" #15 prio=5 os_prio=0 tid=0x00007f01340cc800 waiting on condition [0x00007f00f1efd000]
       java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bf0f7d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    

4. 工具辅助分析

  • VisualVM:可视化分析线程状态和锁竞争。
  • FastThread(在线工具):上传 jstack 日志自动生成分析报告,链接
  • IDEA 插件:如 jstack-analyzer,提供代码级关联。
  • grep/sed/awk:通过命令行快速筛选关键信息(如 grep "BLOCKED" thread_dump.txt)。

5. 常见问题模式

现象可能原因解决方案
大量 BLOCKED 线程锁竞争激烈优化锁粒度或使用无锁数据结构
线程长期 WAITING资源未释放或任务饥饿检查 notify()/notifyAll() 调用
高 CPU 线程处于 RUNNABLE死循环或密集计算优化算法或增加休眠
线程卡在 I/O 操作外部服务响应慢或网络问题优化下游服务或设置超时

6. 注意事项

  • 单次 jstack 日志可能不足以定位问题,需结合多次日志和监控工具(如 jstatjmap)。
  • 线程状态是瞬时的,需结合系统监控(如 CPU、内存、磁盘 I/O)综合分析。
  • 生产环境中建议使用 APM 工具(如 ArthasSkyWalking)实时监控线程状态。

通过以上步骤,可以系统性地分析 jstack 日志,定位性能瓶颈或死锁问题。