死循环导致 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
*/
@RestController
public 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());//1
continue;
}
String pid = partners.substring(colonpos + 1, commapos);
if (pid.length() <= 0) {
//partners = partners.substring(idpos+"partnerid".length());//2
continue;
}
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" 8247
2037
那么拿到这个线程 ID 去搜索刚刚打印出来的内容
# 这里就假装上面找到的线程 id 和实际是一致的
# 也确实是这个线程导致的死循环
"http-nio-8080-exec-1" #26 daemon prio=5 os_prio=31 tid=0x000000014b95e800 nid=0x9803 runnable [0x000000016f83c000]
java.lang.Thread.State: RUNNABLE
at 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;
/**
* 死锁问题
*/
@RestController
public 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 % jps
13810
14739 Jps
14692 Launcher
14693 MonitorJvmDemoApplication
13838 RemoteMavenServer36
anin@anin monitor-jvm-demo % jstack 14693 > 14693.txt
打开文件,直接拉到最后
Found one Java-level deadlock: 直接提示发现一个 java 级死锁,并且有相关的 锁互相持有说明,还有相关的堆栈信息。
由于该场景是我们主动模拟的,比较容易找到,真实的线上场景代码很复杂,可能没有这么容易找出来,但是有了这样的工具,还是比较容易定位到的。