内核调度器将负责调度2种资源种类:线程(单一或者多路)和中断。
调度器去定义不同资源的不同优先权。
优先级:Interrupts(中断) > Kernel(System) Processes(内核处理/系统进程) > User Processes(用户进程)
CPU我们重点关注以下3个部分:
CPU 利用率是定义CPU使用的百分比,主要依赖于是什么资源在试图存取CPU。
评估系统最重要的一个度量方式就是查看CPU的利用率。
要使应用的性能或扩展性达到最高,就必须充分利用分配给它的CPU周期,不能有丝毫浪费。
应用消耗很多CPU并不意味着性能或者扩展性达到了最高,要想找出应用如何使用CPU周期,需要监控系统的CPU使用率。
如果一个CPU被充分使用,利用率分类之间均衡的比例应该是:
CPU利用率的分类如下:
User Time(用户进程时间): 关于在user space中被执行进程在CPU 开销时间百分比。
用户态CPU使用率是指执行应用程序代码的时间占总CPU时间的百分比。
System Time(内核线程以及中断时间) : 关于在kernel space中线程和中断在CPU开销时间百分比。
系统态CPU使用率是指应用执行操作系统调用的时间占总CPU时间的百分比。
系统态CPU使用率高意味着共享资源有竞争或I/O设备之间有大量的交互。
理想情况下,应用达到最高性能和扩展性时,它的系统态CPU使用率为0%。所以要尽可能降低系统态CPU使用率。
Wait IO(IO 请求等待时间) :所有进程线程被阻塞等待完成一次IO请求所占CPU开销idle的时间百分比。
Idle(空闲) : 一个完整空闲状态的进程在CPU 处理器中开销的时间百分比。
vmstat显示所有虚拟处理器的总CPU使用率。
如果不指定vmstat的报告间隔,则输出系统最近一次启动以来所有数据的总和,不过通常可以忽略。
内容 | 说明 |
---|---|
us | 用户态CPU使用率 |
sy | 系统态( 内核和中断)CPU使用率 |
id | 空闲率或CPU可用率 |
wa | 所有可运行状态线程被阻塞在请求等待IO的百分比 |
注:us+sy+id ≈ 100
Linux 内核将双核处理器认为是2个CPU,因此一个双核处理器的双内核会报告有4个CPU可用。
mpstat 命令给出的CPU利用率的统计值大致和vmstat 一致,但是mpstat可以监控每个虚拟处理器的CPU使用率,有助于发现应用中是一些线程比其他线程消耗量更多的CPU周期,还是应用的所有线程基本平分CPU周期。如果是后者,意味着应用的扩展性比较好。
如果不指定mpstat的报告间隔,则输出系统最近一次启动以来所有mpstat数据的总和。
内容 | 说明 |
---|---|
usr | 执行用户代码时所用CPU时间的百分比 |
sys | 执行内核代码时所用CPU时间的百分比 |
iowait | 表示IO等待时间 |
idle | 表示CPU空闲时间的百分比 |
-P 0 表示指定查看第一颗CPU的使用情况(下标从0开始),因为本机只有一颗CPU,所以查看其他CPU会报“处理器实在太多!”的错误提示。 |
每个CPU 都维护一个线程的运行队列。理论上,调度器应该不断的运行和执行线程。进程线程不是在sleep 状态中(阻塞和等待IO中)就是在可运行状态中。
如果CPU子系统处于高负荷下,那就意味着内核调度将无法及时响应系统请求。导致可运行状态进程拥塞在运行队列里。当运行队列越来越巨大,进程线程将花费更多的时间获取被执行。
注:每个处理器应该运行队列不超过1-3个线程。例如,一个双核处理器应该运行队列不要超过6 个线程。
运行队列中是那些已准备好运行、正等待可用CPU的轻量级进程。如果准备运行的轻量级进程数超过系统所能处理的上限,运行队列就会很长。运行队列长表名系统负载可能已饱和。
系统运行队列长度等于虚拟处理器的个数时,用户不会明显感觉到性能下降。此处虚拟处理器的个数就是系统硬件线程的个数。即Java API Runtime.availableProcessors()的返回值。当运行队列长度达到虚拟处理的4倍或更多时,系统的响应就非常迟缓了。
一般性的指导原则是:如果在很长一段时间里,运行队列的长度一直都超过虚拟处理器个数的1倍,就需要关注了,如果3~4倍,需要立刻引起注意或采取行动。
负载是指在CPU 队列中有多少数目的线程,以及当前有多少进程线程数目被执行的组合。
负载 = 正在运行的轻量进程数 + 运行队列中的轻量进程数
例如:一个双核系统执行了2个线程,还有4个在运行队列中,则 load 应该为 6。
top中显示的load averages(平均负载) 分别是指1分钟、5分钟、15 分钟以内的负载情况,如果 CPU 是单核的,则这个数值超过 1 就是高负载,如果 CPU 是四核的,则这个数值超过 4 就是高负载。
经验之谈:如果一个总核数=8核心的CPU,理论上平均负载达到16,也还可以坚持很长一段时间。
vmstat输出的第一列是运行队列长度,值是运行队列中轻量级进程的实际数量。
即使是单核CPU也能支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停的切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务,但是,在切换前保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换,线程上下文切换通常消耗数百个时钟周期。
如何减少上下文切换
1:无锁并发编程,多线程竞争锁时,会引起上下文切换。
2:CAS算法,因为不需要加锁。
3:使用最少线程。
4:协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
多数现代处理器都能够运行一个进程(单一线程)或者线程。多路超线程处理器有能力运行多个线程。然而,Linux 内核还是把每个处理器核心的双核心芯片作为独立的处理器。比如,以Linux 内核的系统在一个双核心处理器上,报告显示为两个独立的处理器。
一个标准的Linux 内核可以运行50 至 50,000 的处理线程。在只有一个CPU时,内核将调度并均衡每个进程线程。每个线程都分配一个在处理器中被开销的时间额度。一个线程要么就是获得时间额度或已抢先获得一些具有较高优先级(比如硬件中断),其中较高优先级的线程将从区域重新放置回处理器的队列中。这种线程的转换关系就是我们提到的上下文切换。
每次内核的上下文切换,资源被用于关闭在CPU寄存器中的线程和放置在队列中。系统中越多的上下文切换,在处理器的调度管理下,内核将得到更多的工作。
上下文切换的数目直接关系到CPU 的使用率,如果CPU 利用率保持在均衡状态时( 65% - 70% User Time、30% - 35% System Time、0% - 5% Idle Time),大量的上下文切换是正常的。
上下文切换分为:
让步式上下文切换是指执行线程主动释放CPU,抢占式上下文切换是指线程因为分配的时间片用尽而被迫放弃CPU或者被其他优先级更高的线程所抢占。
抢占式上下文切换率高表明预备运行的线程数多于可用的虚拟处理器,此时用vmstat通常就能看到很长的运行队列、很高的CPU使用率、很大的迁移数以及大量与之相关的抢占式上下文切换。
pidstat -w报告的是每秒而不是每个测量间隔的让步式上下文切换。
pidstat -w是所有处理器的上下文切换。
cswch/s是让步式上下文切换
nvcswch/s是抢占式上下文切换
一般性准则:如果让步式上下文切换占去它5%或更多的时钟周期,说明Java应用正面临锁竞争,即便占到了3%~5%也值得进一步调查。
Java5以前的版本中,HotSpotVM几乎将所有的锁逻辑都委托给操作系统锁原语,这使得操作系统工具在察看系统态CPU使用率以及smtx(Spin on Mutex互斥量上的自旋次数)的同时,可以很容易地监控Java应用中的锁竞争。
而在Java5中,HotSpotVM以用户代码的方式实现了许多锁优化逻辑、Java同步方法及同步块。所以以前用mpstat查看smtx和sys系统态CPU使用率的方法无法奏效了,需要寻找新的替代方法。
Java5以上,如果若干次忙循环自旋之后没有获取到锁,则挂起该线程,等待被唤醒后再次尝试获取该锁,挂起和唤醒线程会导致操作系统的让步式上下文切换,因此锁竞争严重的应用会表现出大量的让步式上下文切换。
让步式上下文切换耗费的时钟周期代价非常高,通常高达80000个时钟周期(这些时钟周期原本可以用来执行程序指令)。
让步式上下文切换浪费的时钟周期,可以由pidstat -w的让步式上下文切换数除以虚拟处理器的数目而得出。注意:pidstat -w是所有虚拟处理器的让步式上下文切换。让步式上下文切换数乘80000,除以CPU每秒的时钟周期,可以得出让步式上下文切换所耗费的CPU时钟周期百分比。
让步式上下文切换所耗费的时钟周期估算:
例如:让步式上下文切换次数是8000/s,8000*80000(一般上下文切换的时钟周期代价)= 640 000 000个时钟周期,3.0GHz的处理器每秒能执行3000 000 000(30亿)个时钟周期,所以640 000 000/3 000 000 000=21.33%的可用时钟周期。说明Java应用正面临锁竞争,锁竞争可能是因为多个线程正在访问同一个同步方法或同步块,也可能是因为代码被诸如java.util.concurrent.locks.Lock这样的Java锁结构所保护。
例如:cswch/s=3500,有2个处理器,则每个处理器的上下文切换为3500/2=1750,耗费的时钟周期为1750*80000=140 000 000。3GHzCPU每秒的时钟周期数为3 000 000 000,因此上下文切换所浪费的时钟周期为140 000 000/3 000 000 000=4.7%,说明Java应用正面临锁竞争。
即使CPU在等待内存中的数据,操作系统工具仍然会报告CPU繁忙,这种情况被称为停滞。
当CPU执行指令所用的操作数据不在寄存器或者缓存中时,就会发生停滞。
CPU停滞通常会等待(浪费)好几百个时钟周期。
所以提高CPU密集型应用的策略就是减少或者改善CPU高速缓存使用率,从而减少CPU在等待内存数据时浪费的时钟周期。
public class RunTimeTest {
public static void main(String[] args) {
int availableProcessors = Runtime.getRuntime().availableProcessors();
System.out.println(availableProcessors);
}
}
使用 ps 命令通过查看 PSR 这列,检查哪个进程在占用了哪个CPU
while :; do ps -eo pid,ni,pri,pcpu,psr,comm | grep 'java'; sleep 1;done
cat /proc/cpuinfo