在Linux中,CPU主要用于中断、内核以及用户进程的任务处理。
1 基础概念
- 上下文切换
每个CPU在同一时间只能执行一个线程(超线程除外),Linux采用的是抢占式调度,即CPU会给每个线程分配一定的CPU执行时间,当到达执行时间、线程中有IO阻塞或高优先级线程要执行时,Linux将切换执行的线程。
场景:文件IO、网络IO、锁等待或线程Sleep时,当前线程会进入阻塞或者休眠,从而触发上下文切换,切换过多会照成内核占据较多的CPU使用,使得应用的响应速度下降。
- 运行队列
Java启动16个线程,4核CPU,那么平均分配的话,每个CPU的运行队列有4个,当然也不绝对。最好控制在4个以下。
- 利用率
我们一般通过用户进程、内核、中断处理、IO等待以及空闲五个CPU使用百分比,来分析CPU的消耗情况。
正常比例区间:用户进程/内核=65%~70%/30%~35%
2 常用工具
- top
说明:us-用户进程处理占比;sy-内核线程处理占比;ni-nice命令改变优先级的任务占比;id-CPU的空闲时间占比;wa-执行的过程中等待IO占比;hi-硬件中断占比(网卡接收数据频繁);si-软件中断占比。
多核CPU所显示的是所有CPU占比总和,所以可能会超过100%,查看每核CPU消耗,top后按1。top默认展示进程所占CPU,top后按shift+H可以查看每个线程所占CPU,此时PID的值为线程ID值。
发现:计算是错误的,7000的CPU占比?!
那如何查找高CPU占用的线程呢?CPU消耗分析中有样例。
- pidstat
先安装sysstat,如yum install sysstat;
pidstat -p pid -t 间隔多少秒 总共输出多少次
3 CPU消耗分析
- us占用高
原因一:线程一直处于可运行(Runnable)状态,一直循环执行计算;原因二:频繁的GC。
样例:启动8个线程,其中四个不休息持续进行计算任务,另一个计算后休眠1秒。
public class CpuUsHighTest {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
public void run() {
int i = 123;
int j = 321;
while (true) {
int c = i * j;
}
}
}, "no sleep thread").start();
}
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
public void run() {
int i = 123;
int j = 321;
while (true) {
int c = i * j;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "sleep thread").start();
}
}
}
查看top占用:
通过ps定位线程:ps -mp pid -o THREAD,tid,time
将线程ID转化为16进制:
使用jstack来dump线程信息:jstack pid |grep tid -A 30 (-A 30代表显示该tid信息后的30行)
- sy占用高
原因:启动的线程多,且线程大多处于阻塞和运行的状态频繁切换中。
样例:略
4 CPU优化
- us占用高
常见缘由:对超大集合循环遍历,并执行任务;频繁GC
优化方案:添加休眠,避免循环不间断执行任务;JVM调优
- sy占用高
常见缘由:线程数太多;锁竞争激烈
优化方案:增大缓冲队列,减小启动线程数;减少锁竞争
协程(Coroutine):一个线程中可运行大量的协程,而无需进行线程切换,协程间通过消息通信。
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!