死循环导致 CPU 彪高问题
代码实现死循环
package com.example.monitorjvmdemo;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;/**** cpu问题** @author :anin* @date :Created in 2021/12/7 14:43*/@RestControllerpublic class CpuController {/*** 死循环*/@RequestMapping("/loop")public List<Long> loop() {// 由于提取 json 的方法中有 bug ,边界没有考虑清楚// 遇到这种错误的 json 串,就会导致死循环String data = "{\"data\":[{\"partnerid\":]";return getPartneridsFromJson(data);}/*** 一段模拟业务代码,导致死循环的产生。** @param data* @return*/public static List<Long> getPartneridsFromJson(String data) {//{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]}//上面是正常的数据List<Long> list = new ArrayList<>(2);if (data == null || data.length() <= 0) {return list;}int datapos = data.indexOf("data");if (datapos < 0) {return list;}int leftBracket = data.indexOf("[", datapos);int rightBracket = data.indexOf("]", datapos);if (leftBracket < 0 || rightBracket < 0) {return list;}String partners = data.substring(leftBracket + 1, rightBracket);if (partners.length() <= 0) {return list;}while (partners.length() > 0) {int idpos = partners.indexOf("partnerid");if (idpos < 0) {break;}int colonpos = partners.indexOf(":", idpos);// 在这里会导致死循环,int commapos = partners.indexOf(",", idpos);if (colonpos < 0 || commapos < 0) {//partners = partners.substring(idpos+"partnerid".length());//1continue;}String pid = partners.substring(colonpos + 1, commapos);if (pid.length() <= 0) {//partners = partners.substring(idpos+"partnerid".length());//2continue;}try {list.add(Long.parseLong(pid));} catch (Exception e) {//do nothing}partners = partners.substring(commapos);}return list;}}
访问
访问 /loop 服务,就会进入死循环
###GET http://localhost:8080/loop
查看cpu占用率

通过输出信息可以看到,PID 为 13869 这个进程 ID CPU 占用 99.1%
导出文件
anin@anin monitor-jvm-demo % jstack 13869 > 13869.txt
查看进程里的线程
我们还可以通过 top 命令找出指定进程里面的所有 线程
# 可以打印出该进程中的所有线程$ top -H -p 33225# 但是 mac 上 top 没有 -H 参数,看不到

就是如上图所示,不过这个 PID 是 十进制 的,jstack 中打印出来的信息线程 ID 是 十六进制的,也就是 nid=0x2037 这种。
下面可以通过 printf 把十进制转成十六进制
anin@anin ~ % printf "%x\n" 82472037
那么拿到这个线程 ID 去搜索刚刚打印出来的内容
# 这里就假装上面找到的线程 id 和实际是一致的# 也确实是这个线程导致的死循环"http-nio-8080-exec-1" #26 daemon prio=5 os_prio=31 tid=0x000000014b95e800 nid=0x9803 runnable [0x000000016f83c000]java.lang.Thread.State: RUNNABLEat java.lang.String.indexOf(String.java:1772)at java.lang.String.indexOf(String.java:1718)at com.example.monitorjvmdemo.CpuController.getPartneridsFromJson(CpuController.java:59)at com.example.monitorjvmdemo.CpuController.loop(CpuController.java:25)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
首先它的运行状态是 RUNNABLE 的,正在运行中,并且如果你搜索这个 getPartneridsFromJson 的话,会发现有 5 个线程,这里对应的是上面 top 找线程时那个图,排在最前面的最耗 CPU 的线程。这就很容易就定位到了出问题的代码
死锁问题
代码实现死锁
package com.example.monitorjvmdemo;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/*** 死锁问题*/@RestControllerpublic class LockController {private Object lock1 = new Object();private Object lock2 = new Object();/*** 死锁*/@RequestMapping("/deadlock")public String deadlock() {new Thread(() -> {synchronized (lock1) {try {Thread.sleep(1000);} catch (Exception e) {}synchronized (lock2) {System.out.println("Thread1 over");}}}).start();new Thread(() -> {synchronized (lock2) {try {Thread.sleep(1000);} catch (Exception e) {}synchronized (lock1) {System.out.println("Thread2 over");}}}).start();return "deadlock";}}
定义两个线程:
- 第一个线程先锁 lock1,休眠 1 秒,再获取 lock2
 - 另一个线程先锁 lock2,休眠 1 秒,再获取 lock1
 
也就是说,当线程 1 从睡眠中醒来时,lock2 已经被获取过了,就一直等待,而另一个线程一直等待 lock1 的释放。两个线程就互相等待起来了,造成了死锁
访问
访问 /deadlock 服务后,虽然返回了数据,但是程序中其实已经有一个死锁了。
###GET http://localhost:8080/deadlock
分析
这种情况下使用 top 命令查看消耗资源的进程 ID 就不合适了,因为死锁的话,它是等待状态,不怎么耗资源的。
导出文件
anin@anin monitor-jvm-demo % jps1381014739 Jps14692 Launcher14693 MonitorJvmDemoApplication13838 RemoteMavenServer36anin@anin monitor-jvm-demo % jstack 14693 > 14693.txt
打开文件,直接拉到最后
Found one Java-level deadlock: 直接提示发现一个 java 级死锁,并且有相关的 锁互相持有说明,还有相关的堆栈信息。
由于该场景是我们主动模拟的,比较容易找到,真实的线上场景代码很复杂,可能没有这么容易找出来,但是有了这样的工具,还是比较容易定位到的。
