借助jstack分析CPU占用高及线程死锁问题

Laughing
2025-04-18 / 0 评论 / 5 阅读 / 搜一下 / 正在检测是否收录...

jstack是用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因。

一、分析CPU占用高的问题

在实际项目开发过程中,CPU占用异常是经常遇到的问题,当遇到CPU异常时,我们可以借助jstack进行问题排查。

下面我们通过模拟,刨析一下整个问题分析过程。

模拟代码

在下面的代码中,我们写了一个死循环,模拟CPU占用过高问题。

public class ThreadMain {

    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {

        Task task1 = new Task();
        Task task2 = new Task();
        executorService.execute(task1);
        executorService.execute(task2);
    }

    public static Object lock = new Object();

    static class Task implements Runnable {

        public void run() {
            synchronized (lock) {
                long sum = 0L;
                while (true) {
                    sum += 1;
                }
            }
        }
    }

}

查询CPU占用高的进程

在Linux服务器,我们通过top命令,可以查看CPU信息,默认按照由高到低显示。

输入top命令,查询进程信息。

top

m9mgblfe.png

可以看到,目前占用最高的进程ID是9420

查询对应线程

在上面,我们已经查询到9420进程,我们可以进一步分析,9420进程内到底是哪个线程CPU占用最高。

输入一下命令,可以查看线程信息

top Hp 9420

m9mgei4c.png

可以看到目前占用CPU最高的线程是9445

线程转换

我们通过top命令查询到的9445线程ID是10进制的,但是jstack展示的线程是16进制的,因此我们需要将10进制的9445转成16进制,也就是0x24e5

通过jstack查询线程快照

输入一下命令

jstack 9420

m9mgjwd3.png

jstack输出信息的nid就是16进制的线程id,我们找到nid0x24e5线程,可以定位到具体代码位置 at ThreadMain$Task.run(ThreadMain.java:30),也就是ThreadMain.java的第30行。

二、分析线程死锁问题

模拟代码

我们通过下面的代码,模拟线程死锁问题

public class ThreadMain {

    private final static Object lockA = new Object();

    private final static Object lockB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "获得了锁LockA");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    try {
                        throw new RuntimeException(e);
                    } catch (RuntimeException ex) {
                        throw new RuntimeException(ex);
                    }
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "获得了锁LockB");
                }
            }
        }).start();


        new Thread(() -> {
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "获得了锁LockB");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    try {
                        throw new RuntimeException(e);
                    } catch (RuntimeException ex) {
                        throw new RuntimeException(ex);
                    }
                }
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获得了锁LockA");
                }
            }
        }).start();
    }
}

查询VMID

我们通过jps命令,可以查询虚拟机进程ID

jps -l

m9mgsgct.png

可以看到,我们当前VMID是18395

查询线程信息

同样通过jstack命令可以查询进程信息。

jstack 18395

m9mgvo1w.png

重点关注状态时BLOCKED的线程,我们可以看到Thread-1Thread-1目前都是BLOCKED状态,Thread-1目前锁定了<0x0000000782539600>并且正在等待<0x00000007825395f0>,而Thread-0正好相反,Thread-0锁定了<0x00000007825395f0>并且正在等待<0x0000000782539600>,这就导致两个线程相互锁定。当然,我们也可以看到两个线程异常发生的位置。

0

评论 (0)

取消