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
可以看到,目前占用最高的进程ID是9420
查询对应线程
在上面,我们已经查询到9420
进程,我们可以进一步分析,9420
进程内到底是哪个线程CPU占用最高。
输入一下命令,可以查看线程信息
top Hp 9420
可以看到目前占用CPU最高的线程是9445
线程转换
我们通过top
命令查询到的9445
线程ID是10进制的,但是jstack
展示的线程是16进制的,因此我们需要将10进制的9445
转成16进制,也就是0x24e5
通过jstack查询线程快照
输入一下命令
jstack 9420
jstack
输出信息的nid
就是16进制的线程id,我们找到nid
是0x24e5
线程,可以定位到具体代码位置 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
可以看到,我们当前VMID是18395
查询线程信息
同样通过jstack
命令可以查询进程信息。
jstack 18395
重点关注状态时BLOCKED
的线程,我们可以看到Thread-1
跟Thread-1
目前都是BLOCKED
状态,Thread-1
目前锁定了<0x0000000782539600>
并且正在等待<0x00000007825395f0>
,而Thread-0
正好相反,Thread-0
锁定了<0x00000007825395f0>
并且正在等待<0x0000000782539600>
,这就导致两个线程相互锁定。当然,我们也可以看到两个线程异常发生的位置。
评论 (0)