CPU占用很高排查方案
# CPU占用很高排查方案
说明:一般CPU占用很高是由于线程并发过程中,导致的线程阻塞、死锁等等。设计到线程的测试建议放大虚拟机中执行。
排查的过程,主要是使用到了jstack
命令。
# 案例演示
死锁代码:
public class JstackDeadLockDemo {
/**
* 必须有两个可以被加锁的对象才能产生死锁,只有一个不会产生死锁问题
*/
private final Object obj1 = new Object();
private final Object obj2 = new Object();
public static void main(String[] args) {
new JstackDeadLockDemo().testDeadlock();
}
private void testDeadlock() {
Thread t1 = new Thread(() -> calLock_Obj1_First());
Thread t2 = new Thread(() -> calLock_Obj2_First());
t1.start();
t2.start();
}
/**
* 先synchronized obj1,再synchronized obj2
*/
private void calLock_Obj1_First() {
synchronized (obj1) {
sleep();
System.out.println("已经拿到obj1的对象锁,接下来等待obj2的对象锁");
synchronized (obj2) {
sleep();
}
}
}
/**
* 先synchronized obj2,再synchronized obj1
*/
private void calLock_Obj2_First() {
synchronized (obj2) {
sleep();
System.out.println("已经拿到obj2的对象锁,接下来等待obj1的对象锁");
synchronized (obj1) {
sleep();
}
}
}
/**
* 为了便于让两个线程分别锁住其中一个对象,
* 一个线程锁住obj1,然后一直等待obj2,
* 另一个线程锁住obj2,然后一直等待obj1,
* 然后就是一直等待,死锁产生
*/
private void sleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
把代码上传到Linux系统进行编译运行执行,如果linux报错如下:
Error: Could not find or load main class JstackDeadLockDemo
解决方案:在系统的etc/profile
或者用户的~/.profile
中的增加当前目录的配置,即.:
export CLASSPATH=.:$JAVA_HOME/lib/tools.jar
此外,记得删除源码的package路径。运行结果:
可以看到,程序依然处于运行状态,现在我们知道是线程死锁造成的问题。
# 问题分析
如果是生产环境的话,怎么样才能发现目前程序有问题呢?我们可以推导一下,如果线程死锁,那么线程一直在占用CPU,这样就会导致CPU一直处于一个比较高的占用率。所示我们解决问题的思路应该是:
- 首先查看Java进程ID(jps、jcmd命令)
- 根据进程 ID 检查当前使用异常线程的pid (
top -Hp ID
,根据进程 ID 检查当前使用异常线程的pid) - 把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
jstack <pid> | grep -A20 0x7bcf
得到相关进程的代码
执行1、2步如下图,可以看得出来当前占用CPU比较高的线程ID是34864,转换为16进制 (opens new window)为:0x8830(后面dump线程日志会使用)
鉴于我们当前代码量比较小,线程也比较少,所以我们就把所有的信息全部导出来:jstack 34864 > jstack.log
。打开jstack.log
文件查找一下刚刚我们转换完的16进制线程ID,0x8830是否存在 :
可以看的出来jstack
命令生成的thread dump信息包含了JVM中所有存活的线程,里面确实是存在我们定位到的线程 ID ,在thread dump中每个线程都有一个nid,在nid=0x8830的线程调用栈中,我们发现两个线程在互相等待对方释放资源:
到此就可以检查对应的代码是否有问题,也就定位到我们的死锁问题。
xx大厂问题排查过程
......
4、ps aux | grep java
查看到当前Java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
5、top -Hp 进程pid
检查当前使用异常线程的pid
6、把线程pid变为16进制如 31695 - 》 7bcf 然后得到0x7bcf
7、jstack 进程的pid | grep -A20 0x7bcf
得到相关进程的代码
# 解决
调整锁的顺序,保持一致或者采用定时锁,一段时间后,如果还不能获取到锁就释放自身持有的所有锁。