cpu如何计算java
当咱们执行top命令的时候,看到里面的值(主要是cpu和load)值是一直在变的,所以有必要简单了解一下Linux系统中cpu的计算方式。ios
cpu分为系统cpu和进程、线程cpu,系统cpu的统计值位于/proc/stat下(如下的截图未截全):数据库
cpu、cpu0后面的这些数字都和前面的us、sy、ni这些对应,具体哪一个对应哪一个值不重要,感兴趣的能够网上查一下文档。服务器
进程cpu的统计值位于/proc/{pid}/stat下:网络
线程cpu的统计值位于/proc/{pid}/task/{threadId}/stat下: 多线程
这里面的全部值都是从系统启动时间到当前时间的一个值。所以,对于cpu的计算的作法是,采样两个足够短的时间t一、t2:架构
其余时间例如us、sy、ni都是相似的计算方式,总结起来讲,cpu这个值反应的是某个采样时间内的cpu使用状况。所以有时候cpu很高,可是打印线程堆栈出来发现高cpu的线程在查询数据库等待中,不要以为奇怪,由于cpu统计的是采样时间内的数据。并发
假设top观察某段时间用户空间cpu一直很高,那么意味着这段时间用户的程序一直在占据着cpu作事情。框架
对load的理解工具
关于load的含义,其实有些文章把它跟行车过桥联系在一块儿是比较恰当和好理解的:
一个单核的处理器能够形象得比喻成一条单车道,车辆依次行驶在这条单车道上,前车驶过以后后车才能够行驶。
若是前面没有车辆,那么你顺利经过;若是车辆众多,那么你须要等待前车经过以后才能够经过。 所以,须要些特定的代号表示目前的车流状况,例如: ·等于0.00,表示目前桥面上没有任何的车流。实际上这种状况0.00和1.00之间是相同的,总而言之很通畅,过往的车辆能够丝绝不用等待的经过 ·等于1.00,表示恰好是在这座桥的承受范围内。这种状况不算糟糕,只是车流会有些堵,不过这种状况可能会形成交通愈来愈慢 ·大于1.00,那么说明这座桥已经超出负荷,交通严重的拥堵。那么状况有多糟糕? 例如2.00的状况说明车流已经超出了桥所能承受的一倍,那么将有多余过桥一倍的车辆正在焦急的等待
可是比喻终归是比喻,从比喻中咱们了解了,load表示的是系统的一个能力,可是咱们殊不知道什么样的任务会被归到load的计算中。关于具体怎么样的任务会被归到load的计算中,可使用man uptime命令看一下Linux对于load的解释:
大体意思就是说,系统load是处于运行状态或者不可中断状态的进程的平均数(标红部分表示被算入load的内容)。一个处于运行状态的进程表示正在使用cpu或者等待使用cpu,一个不可中断状态的进程表示正在等待IO,例如磁盘IO。load的平均值经过3个时间间隔来展现,就是咱们看到的1分钟、5分钟、15分钟,load值和cpu核数有关,单核cpu的load=1表示系统一直处在负载状态,可是4核cpu的load=1表示系统有75%的空闲。
特别注意,load指的是全部核的平均值,这和cpu的值是有区别的。
还有一个重要的点是,查了资料发现,虽然上面一直强调的是"进程",可是进程中的线程数也是会被看成不一样的进程来计算的,假如一个进程产生1000个线程同时运行,那运行队列的长度就是1000,load average就是1000。
请求数和load的关系
以前我本身一直有个误区:当成千上万的请求过来,且在排队的时候,后面的请求得不处处理,load值必然会升高。认真思考以后,这个观点可真是大错特错,所以特别做为一段写一下和你们分享。
以Redis为例,咱们都知道Redis是单线程模型的,这意味着同一时间能够有无数个请求过来,可是同一时间只有一个命令会被处理(图片来源https://www.processon.com/view/5c2ddab0e4b0fa03ce89d14f):
单独的一条线程接到就绪的命令以后,会将命令转给事件分发器,事件分发器根据命令的类型执行对应的命令处理逻辑。因为只有一条线程,只要后面排队的命令足够多到让这条线程一个接一个不停地处理命令,那么load表现就等于1。
整个过程当中,回看load这个值,它和请求数没有任何关系,真正和load相关的是工做线程数量,main线程是工做线程、Timer是工做线程、GC线程也是工做线程,load是以线程/进程做为统计指标,不管请求数是多少,最终都须要线程去处理,而工做线程的处理性能直接决定了最终的load值。
举个例子,假设一个服务中有一个线程池,线程池中线程数量固定为64:
所以,总而言之,搞清楚load值和请求数、线程数的关系很是重要,想清楚这些才能正确地进行下一步的工做。
load高、cpu高的问题排查思路
首先抛出一个观点:cpu高不是问题,由cpu高引发的load高才是问题,load是判断系统能力指标的依据。
为何这么说呢,以单核cpu为例,当咱们平常cpu在20%、30%的时候其实对cpu资源是浪费的,这意味着绝大多数时候cpu并无在作事,理论上来讲一个系统极限cpu利用率能够达到100%,这意味着cpu彻底被利用起来了处理计算密集型任务,例如for循环、md5加密、new对象等等。可是实际不可能出现这种状况,由于应用程序中不消耗cpu的IO不存在是几乎不可能的,例如读取数据库或者读取文件,所以cpu不是越高越好,一般75%是一个须要引发警惕的经验值。
注意前面提到的是"引发警惕",意味着cpu高不必定是问题,可是须要去看一下,尤为是平常的时候,由于一般平常流量不大,cpu是不可能打到这么高的。若是只是普通的代码中确实在处理正常业务那没问题,若是代码里面出现了死循环(例如JDK1.7中经典的HashMap扩容引起的死循环问题),那么几条线程一直占着cpu,最后就会形成load的增高。
在一个Java应用中,排查cpu高的思路一般比较简单,有比较固定的作法:
网上有不少文章写到这里就停了,实践过程当中并非这样。由于cpu是时间段内的统计值、jstack是一个瞬时堆栈只记录瞬时状态,两个根本不是一个维度的事,所以彻底有可能从打印出来的堆栈行号中看到代码停留在如下地方:
若是彻底按照上面那一套步骤作的话碰到这种状况就傻眼了,左思右想半天却不得其解,根本不明白为何这种代码会致使高cpu。针对可能出现的这种状况,实际排查问题的时候jstack建议打印5次至少3次,根据屡次的堆栈内容,再结合相关代码段进行分析,定位高cpu出现的缘由,高cpu多是代码段中某个bug致使的而不是堆栈打印出来的那几行致使的。
另外,cpu高的状况还有一种可能的缘由,假如一个4核cpu的服务器咱们看到总的cpu达到了100%+,按1以后观察每一个cpu的us,只有一个达到了90%+,其余都在1%左右(下图只是演示top按1以后的效果并不是真实场景):
这种状况下能够重点考虑是否是频繁FullGC引发的。由于咱们知道FullGC的时候会有Stop The World这个动做,多核cpu的服务器,除了GC线程外,在Stop The World的时候都是会挂起的,直到Stop The World结束。以几种老年代垃圾收集器为例:
不管如何,当真正发生Stop The World的时候,就会出现GC线程在占用cpu工做而其余线程挂起的状况,天然表现也就为某个cpu的us很高并且他cpu的us很低。
针对FullGC的问题,排查思路一般为:
若是FullGC只是发生在老年代区,比较有经验的开发人员仍是容易发现问题的,通常都是一些代码bug引发的。MetaSpace发生的FullGC常常会是一些诡异、隐晦的问题,不少和引入的第三方框架使用不当有关或者就是第三方框架有bug致使的,排查起来就很费时间。
那么频繁FullGC以后最终会致使load如何变化呢?这个我没有验证过和看过具体数据,只是经过理论分析,若是全部线程都是空闲的,只有GC线程在一直作FullGC,那么load最后会趋近于1。可是实际不可能,由于若是没有其余线程在运行,怎么可能致使频繁FullGC呢。因此,在其余线程处理任务的状况下Stop The World以后,cpu挂起,任务得不处处理,更大可能的是load会一直升高。
最后顺便提一句,前面一直在讲FullGC,频繁的YoungGC也是会致使load升高的,以前看到过的一个案例是,Object转xml,xml转Object,代码中每处都new XStream()去进行xml序列化与反序列化,回收速度跟不上new的速度,YoungGC次数陡增。
load高、cpu低的问题排查思路
关于load的部分,咱们能够看到会致使load高的几个因素:
既然cpu不高,load高,那么线程要么在进行io要么在等待使用cpu。不过对于后者"等待使用cpu"我这里存疑,好比线程池里面10个线程,任务来的很慢,每次只会用到1个线程,那么9个线程都是在等待使用cpu,可是这9个线程明显是不会占据系统资源的,所以我认为天然也不会消耗cpu,因此这个点不考虑。
所以,在cpu不高的状况下假如load高,大几率io高才是罪魁祸首,它致使的是任务一直在跑,迟迟处理不完,线程没法回归线程池中。首先简单讲讲磁盘io,既然wa表示的是磁盘io等待cpu的百分比,那么咱们能够看下wa确认下是否是磁盘io致使的:
若是是,那么按照cpu高一样的方式打印一下堆栈,查看文件io的部分进行分析,排查缘由,例如是否是多线程都在读取本地一个超大的文件到内存。
磁盘io致使的load高,我相信这毕竟是少数,由于Java语言的特色,应用程序更多的高io应当是在处理网络请求,例如:
针对这种状况,我以为首先咱们应该对整个系统架构的依赖比较熟悉,例如我画一个草图:
对依赖方的调用任何一个出现比较高的耗时都会增长自身系统的load,出现load高的建议排查方式为:
若是上面的步骤仍是没用或者没有对接口调用作埋点,那么仍是万能的打印堆栈吧,连续打印五次十次,看一下每次的堆栈是否大多都指向同一个接口的调用,网络io的话,堆栈的最后几行通常都有at java.net.SocketInputStream.read(SocketInputStream.java:129)。
Java应用load高的几种缘由总结
前面说了这么多,这里总结一下load高常见的、可能的一些缘由:
线上遇到问题的时候首先不要慌,由于大部分load高的问题都集中在以上几个点里面,如下分析问题的步骤或许能帮你整理思路:
最后仍是不行,当一筹莫展时,jstack打印堆栈多分析分析吧,或许能灵光一现能找到错误缘由。
结语
先有理论,把理论想透了,实战碰到问题的时候才能头脑清楚。
坦白讲,cpu和load高排查是一个很偏实战的事情,这方面我还也有很长一条路须要走,身边在这块经验比我丰富的同事多得很。不少人有问过我,项目比较简单,根本没有这种线上问题须要我去排查怎么办?这个问题只能说,平时多积累、多实战是惟一途径,假如没有实战机会,那么推荐三种方式:
当真的有实战机会来的时候把握住,即便是同事排查的问题,也能够在过后搞清楚问题的前因后果,长此以往天然这方面的能力就会提升上去。