本文主要列举了如下几种可能造成CPU过高的场景进行排查分析。
1、代码死循环
启动了两个线程(线程一定要起一个合适的名称,出了问题时方便排查),一个线程空循环,一个线程每500ms循环一次。
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
}
});
t1.setName("t1 thread");
t1.start();
Thread t2 = new Thread(() -> {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.setName("t2 thread");
t2.start();
}
}
丢到服务器运行,top命令查看cpu使用情况,发现pid为15661的java进程CPU使用率达到99.9%。
继续使用top -Hp 15661查看线程状况,发现是15671的线程占用了CPU。
接下来使用jstack 15661命令,输出虚拟机当前时刻线程快照信息,jstack输出中线程id是以16进制显示,用刚才找到的使用CPU最高的线程15671,转换为16进制后就是0x3d37,直接定位到就是名称为“t1 thread”的这个线程。
2、线程过多
启动1000个线程,每个线程每休眠10ms执行一次。
public class MoreThread {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName("number:" + i + ", thread");
t.start();
}
}
}
排查方式和上一个一样
进程下有大量的线程
jstack查看,线程都处于sleep状态,cpu忙于调度这1000个线程,导致使用率过高。
3、频繁YGC,FullGC
配置堆内存为32m,main线程每1ms需要申请一个5m大小的内存空间,导致需要频繁的垃圾回收。
-Xms32m
-Xmx32m
public class MoreFullGC {
public static void main(String[] args) throws InterruptedException {
while (true) {
Thread.sleep(1);
byte[] b = new byte[1024 * 1024 * 5];
}
}
}
前面都是一样的排查方式
21180和21179分别对应16进制0x52bb、0x52bc,这次发现一个main线程,因为main线程在无限循环,还有一个是VM Thread,当看到这个线程时就差不多可以联想是GC的问题了。
通过jstat命令,看一下垃圾回收的情况。
jstat -gc 21178 1000 10,每1秒一次输出指定进程下虚拟机当前内存和垃圾收集状况,输出10次。
YGC和FGC,差不多每秒200次。
总结
当遇到CPU过高时,基本排查思路就是先通过top命令,确认是否是我们的java进程,如果是则根据进程ID,再找到进程中耗时较高的线程id,再通过jstack命令输出当前虚拟机栈的状况进行匹配(jstack线程id为16进制,需要转换匹配),排查具体的代码,如果发现线程是VM Thread线程,那就通过jstat命令确认垃圾回收情况,如果要排查FullGC较高的原因,一般可以通过jmap、mat等工具再进行分析。