使用jstack排查死锁,面试考点

0 阅读1分钟

使用 jstack 排查死锁

jstack 是 JDK 自带的命令行工具,用于生成 Java 进程的线程快照(thread dump)。通过分析线程快照,可以快速定位死锁问题。

1. 获取 Java 进程 ID

首先,找到目标 Java 进程的 PID(进程 ID)。使用 jps 命令:

jps -l

输出示例:

12345 com.example.MyApplication
67890 sun.tools.jps.Jps

记录下应用进程的 PID,例如 12345

2. 生成线程快照

使用 jstack 命令生成线程快照:

jstack <pid> > thread_dump.txt

例如:

jstack 12345 > thread_dump.txt

如果应用因死锁导致无响应,可以添加 -l 选项(显示锁信息):

jstack -l 12345 > thread_dump.txt

注意:在某些环境下(如容器),可能需要使用 sudo 或以应用运行用户身份执行。

3. 分析线程快照

打开生成的 thread_dump.txt,查找死锁信息。

3.1 查找死锁摘要

jstack 会在线程快照末尾自动检测并输出死锁摘要,类似:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f8b5c00a5a8 (object 0x00000000d5f6e3a0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f8b5c00b0b0 (object 0x00000000d5f6e3b0, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.example.DeadlockExample.method2(DeadlockExample.java:25)
        - waiting to lock <0x00000000d5f6e3a0> (a java.lang.Object)
        - locked <0x00000000d5f6e3b0> (a java.lang.Object)
        at com.example.DeadlockExample.run(DeadlockExample.java:15)
"Thread-0":
        at com.example.DeadlockExample.method1(DeadlockExample.java:18)
        - waiting to lock <0x00000000d5f6e3b0> (a java.lang.Object)
        - locked <0x00000000d5f6e3a0> (a java.lang.Object)
        at com.example.DeadlockExample.run(DeadlockExample.java:10)

3.2 手动分析线程堆栈

如果没有自动摘要(例如使用 jstack 的旧版本),可以手动查找以下特征:

  • BLOCKED 状态:线程状态为 BLOCKED(在 java.lang.Thread.State 中)
  • 等待锁:堆栈中包含 waiting to lock <0x...>locked <0x...>

找出相互等待锁的线程对,即线程 A 持有锁 L1 等待锁 L2,线程 B 持有锁 L2 等待锁 L1。

4. 解读死锁信息

从摘要中可以获取:

  • 死锁线程名称Thread-1Thread-0
  • 锁对象地址<0x00000000d5f6e3a0><0x00000000d5f6e3b0>
  • 持有与等待关系
    • Thread-1 持有 <0x...b0>,等待 <0x...a0>
    • Thread-0 持有 <0x...a0>,等待 <0x...b0>
  • 代码位置:具体到哪个类的哪一行(如 DeadlockExample.java:25

5. 解决死锁

根据堆栈定位到代码,常见解决方式:

  • 调整锁顺序:确保所有线程以相同的顺序获取锁
  • 使用 tryLock:尝试获取锁,超时则释放已持有的锁
  • 减少锁粒度:缩小同步块范围
  • 使用更高层次的并发工具:如 ReentrantLockStampedLockConcurrentHashMap

6. 完整示例

以下是一个简单的死锁代码及使用 jstack 排查的演示。

6.1 死锁代码

public class DeadlockExample implements Runnable {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    private void method1() {
        synchronized (lock1) {
            System.out.println("Thread-0 持有 lock1");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock2) {
                System.out.println("Thread-0 持有 lock2");
            }
        }
    }

    private void method2() {
        synchronized (lock2) {
            System.out.println("Thread-1 持有 lock2");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock1) {
                System.out.println("Thread-1 持有 lock1");
            }
        }
    }

    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();
        Thread t1 = new Thread(example, "Thread-0");
        Thread t2 = new Thread(example, "Thread-1");
        t1.start();
        t2.start();
    }
}

6.2 使用 jstack 排查

# 1. 找到 PID
jps -l

# 2. 生成快照
jstack -l 12345 > deadlock.txt

# 3. 查看死锁摘要
grep -A 20 "Found one Java-level deadlock" deadlock.txt

输出类似:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f8b5c00a5a8 (object 0x00000000d5f6e3a0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f8b5c00b0b0 (object 0x00000000d5f6e3b0, a java.lang.Object),
  which is held by "Thread-1"

7. 注意事项

  • 如果进程占用 CPU 过高,也可用 jstack 查看线程状态,结合 top -H -p <pid> 找到高 CPU 线程 ID 转十六进制后匹配堆栈。
  • 多次生成快照(间隔几秒)有助于对比线程状态变化。
  • 在容器中,可能需要进入容器内部执行 jstack,或使用 docker exec
  • jstack 不可用(如 JVM 未安装完整 JDK),可使用 kill -3 <pid> 将线程快照输出到标准错误(通常记录在应用日志中)。

通过以上步骤,可以快速定位并解决 Java 应用中的死锁问题。