CPU飙高排查
# 1 前言
线上应用CPU飙高,使用几个简单命令快速定位问题代码。
# 2 Demo
# 2.1 代码示例
Java代码示例(CPU、内存飙高)
public class TestMemCpu {
//是否跑消耗内存代码的标记,默认否,即跑消耗Cpu的代码
private static boolean memConsume = false;
// 消耗 Cpu 命令
// java -XX:InitialHeapSize=64m -XX:MaxHeapSize=64m -XX:NewSize=32m -XX:MaxNewSize=32m
// 消耗内存命令
// java -XX:InitialHeapSize=4096m -XX:MaxHeapSize=4096m -XX:NewSize=3072m -XX:MaxNewSize=3072m -XX:+UseConcMarkSweepGC -XX:PretenureSizeThreshold=500m -cp "." ResouceManageService -m 1 &
public static void main(String[] args) {
final ResouceManageService service = new ResouceManageService();
int num = 1;
for (int i = 0; i < args.length; i++) {
//指定-m表明跑消耗内存,指定-c或不指定为消耗Cpu,
if ("-c".equals(args[i])) {
} else if ("-m".equals(args[i])) {
memConsume = true;
num = Integer.parseInt(args[i + 1]);
i++;
}
}
int finalNum = num;
new Thread(()->{
if(memConsume){
service.memConsume(finalNum);
}else {
service.cpuConsume();
}
}, "thread-louis-100").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ResouceManageService {
//只内存消耗调用这个方法
@SuppressWarnings("unchecked")
public void memConsume(int num){
//执行一个for循环进行新生代内存的申请,共消耗num数量GB
for(int i=0;i<num * 10;i++){
@SuppressWarnings("rawtypes")
Vector v = new Vector();
byte b1[] = new byte[104857600]; //100M
v.add(b1);
}
//内存消耗申请完后,执行死循环休眠,让JVM一直占用申请到的内存,达到一直占用num数量GB的效果
while(true){
try {
Thread.sleep(3600000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//cpu消耗方法
public void cpuConsume(){
// 角度的分割
final double SPLIT = 0.01;
// 2PI分割的次数,也就是2/0.01个,正好是一周
final int COUNT = (int) (2 / SPLIT);
final double PI = Math.PI;
// 时间间隔
final int INTERVAL = 200;
long[] busySpan = new long[COUNT];
long[] idleSpan = new long[COUNT];
int half = INTERVAL / 2;
double radian = 0.0;
for (int i = 0; i < COUNT; i++) {
busySpan[i] = (long) (half + (Math.sin(PI * radian) * half));
idleSpan[i] = INTERVAL - busySpan[i];
radian += SPLIT;
}
long startTime = 0;
int j = 0;
while (true) {
j = j % COUNT;
startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < busySpan[j]);
try {
//这里的if控制可以注解掉,让Thread.sleep(idleSpan[j])一直执行。
//我这里加了if控制是因为希望Cpu一直保存在70%以上工作的效果(小于70不sleep),If注解掉将以正弦曲线的趋势使用Cpu
if(idleSpan[j]<70){
Thread.sleep(idleSpan[j]);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 2.2 IDEA构建jar包




# 2.3 程序运行
java -XX:InitialHeapSize=64m -XX:MaxHeapSize=64m -XX:NewSize=32m -XX:MaxNewSize=32m -jar mydemo.jar
# 3 排查
# 3.1 查看cpu情况
top

可以看到有个 PID
为 30064
的进程占用CPU过高。
# 3.2 查看进程信息
ps -mp 30064 -o THREAD,tid,time
# 属性介绍:tid代码线程ID,time代表这个线程的已运行时间,30064为对应pid的值。
2
3

可以看到 30076
的线程cpu占用率最高。
# 3.3 将线程ID转为16进制
提示
因为jvm运行信息都是16进制,所以,这里需要将10进制转为16进制。
printf "%x\n" 30076
757c
2
3
jstack 30064 | grep 757c -A30

定位到问题可能出现在线程
thread-louis-100
。
提示
代码中创建线程时加上命名,可帮助快速定位问题。
# 3.4 线程状态说明
线程的Thread状态 java.lang.Thread.State
内容说明:
一个Thread对象可以有多个状态,在 java.lang.Thread.State
中,总共定义六种状态:
1、NEW
线程刚刚被创建,也就是已经 new
过了,但是还没有调用 start()
方法, jstack
命令不会列出处于此状态的线程信息。
2、RUNNABLE
RUNNABLE这个名字很具有欺骗性,很容易让人误以为处于这个状态的线程正在运行。事实上,这个状态只是表示,线程是可运行的。我们已经无数次提到过,一个单核CPU在同一时刻,只能运行一个线程。
3、BLOCKED
线程处于阻塞状态,正在等待一个monitor lock。通常情况下,是因为本线程与其他线程公用了一个锁。其他在线程正在使用这个锁进入某个synchronized同步方法块或者方法,而本线程进入这个同步代码块也需要这个锁,最终导致本线程处于阻塞状态。
4、WAITING
等待状态,调用以下方法可能会导致一个线程处于等待状态:
例如:对于wait()方法,一个线程处于等待状态,通常是在等待其他线程完成某个操作。
本线程调用某个对象的wait()方法,其他线程处于完成之后,调用同一个对象的notify或者notifyAll()方法。
Object.wait()方法只能够在同步代码块中调用。调用了wait()方法后,会释放锁。
5、TIMED_WAITING
线程等待指定的时间,对于以下方法的调用,可能会导致线程处于这个状态:
Thread.sleep #java.lang.Thread.State: TIMED_WAITING (sleeping)
Object.wait 指定超时时间 #java.lang.Thread.State: TIMED_WAITING (on object monitor)
Thread.join with timeout
LockSupport.parkNanos #java.lang.Thread.State: TIMED_WAITING (parking)
LockSupport.parkUntil #java.lang.Thread.State: TIMED_WAITING (parking)
2
3
4
5
6
7
8
9
6、TERMINATED
线程终止。
# 4 使用阿尔萨斯排查
# 4.1 下载并运行arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 4.2 找出最忙的线程
thread -n 3