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

使用 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 应用中的死锁问题。

相关推荐
Lhappy嘻嘻44 分钟前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
伊玛目的门徒1 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
懒鸟一枚4 小时前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
我命由我123455 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
考虑考虑6 小时前
Sentinel安装
java·后端·微服务
凤凰院凶涛QAQ7 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
掘金_答案8 小时前
上线那天,一个 ConcurrentHashMap 差点送走我的 AI 客服——3 天排查 JVM 血泪史
java·后端·架构
猿与禅8 小时前
CosId 分布式 ID 生成器完全教程:从架构原理到生产落地
java·shardingsphere·雪花算法·分布式id·高性能·cosid·号段模式
MindUp8 小时前
企业网盘权限模型解析:多层级访问控制与审计能力选型指南
java·linux·运维
NG4779 小时前
【软件设计与体系结构】-策略设计模式
java·设计模式·软件工程