相关文章:
这里我们通过 jstack 来分析下死循环和死锁的问题
一、死循环示例
-
CpuController.java
@RestController public class CpuController { @GetMapping(value = "/loop/{data}") public String loop(@PathVariable("data") String data) { while (true) { int index = data.indexOf(","); if (index > -1) { break; } } return "loop over!"; } }
-
如上所示,在 Controller 层里,我们编写了一个 loop() 方法,当传入的 data 数据为空时,该方法会陷入死循环
二、死锁示例
-
CpuController.java
@RestController public class CpuController { private final Object lock1 = new Object(); private final Object lock2 = new Object(); @GetMapping(value = "/deadlock") public String deadlock() { Thread threadA = new Thread(() -> { synchronized (lock1) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("threadA over!"); } } }); threadA.start(); Thread threadB = new Thread(() -> { synchronized (lock2) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("threadB over!"); } } }); threadB.start(); return "deadlock"; } }
-
如上所示,在 Controller 层里,我们编写了一个 deadlock() 方法,该方法中 new 了两个线程 A 和 B,线程 A 获取到了 lock1 锁,线程 B 获取到了 lock2 锁,在休眠了 1s 后,线程 A 和线程 B 在对方没有释放锁的情况,去申请获取对方拥有的锁,从而造成死锁
三、打包部署
- 为了展示方便,我们将项目打包部署到 Linux 服务器上并运行,这里我指定的端口号为 9998
四、分析死循环
- 项目启动完毕后,我们在浏览器中,打开 5 个页面来调用 loop() 方法
-
接着我们通过
top
命令来查看服务器中各个进程的资源占用情况
-
如上所示,此时 CPU 的占用率已经非常高了,主要是由于 16618 这个进程导致的,其 CPU 占用率高达 99.0%
-
我们先通过
jstack 16618 > 16618.txt
命令将其线程堆栈信息保存到 16618.txt 文件中,并将其下载到本地
-
-
然后我们再通过
top -p 16618
命令来查看该进程内部线程的 CPU 占用率
-
如上所示,我们可以很清楚地看到该进程内部有 5 个线程的 CPU 占用率非常高,其 pid 分别为:16643、16641、16642、16637、16640
-
此处展示的线程 pid 是 10 进制的,而生成的线程堆栈信息中,线程 pid 是通过 16 进行来展示的,因此我们需要将其转换一下
-
10进制 pid 对应的 16 进制 pid 如下所示
十进制 十六进制 16643 4103 16641 4101 16642 4102 16637 40fd 16640 4100
-
-
最后我们在本地打开之前下载的 16618.txt 文件,搜索 4103
- 如上所示,我们定位到了该线程,其内存堆栈显示该线程调用了 CpuController 类的 loop() 方法,同理搜索其余四个线程 pid,可以得到相同的结果
-
至此,我们已经成功通过 jstack 定位到了死循环发生的地方 (导致 CPU 占用率过高),不过在实际生产环境中,造成 CPU 占用率过高的问题可能会相对复杂,需要进行仔细分析才能解决
五、分析死锁
-
分析完死循环后,我们再来分析死锁,先重启下项目,然后在浏览器中调用 deadlock() 方法
-
同上面的步骤,我们通过 jstack 来生成线程堆栈文件,下载到本地后打开
-
将文件拉到底,可以看到 jstack 已经帮我们找到了死锁,其发生在 CpuController 类中
-
Thread-5 锁住的是 <0x00000000ed793638>,想要再去锁住 <0x00000000ed793628>,而 Thread-4 锁住的是 <0x00000000ed793628>,想要再去锁住 <0x00000000ed793638> ,从而造成死锁
-
-
至此,我们已经成功通过 jstack 定位到了死锁发生的地方,不过在实际生产环境中,造成死锁的问题可能会相对复杂,需要进行仔细分析才能解决