2

有时候好好的程序放到生产服务器上一段时间后,就会发现服务器响应缓慢,进而进一步发现是cpu过高,于是就慌了,造成cpu过高的原因很多,不过大多是由于资源吃紧造成,例如:sql执行过慢,程序里存在死循环,数据库连接未释放,网络阻塞导致的第三方框架代码出现死循环,大量的操作导致死锁等

案例:死循环造成CPU过高

public class CpuTest {
 
    public static void main(String[] args) throws InterruptedException {
        loop();
        endlessLoop();
    }
 
 
    public static void endlessLoop() throws InterruptedException {
 
        while (true) {
            System.out.println("hello world! loop!");
        }
    }
 
    public static void loop(){
        for (int i = 0; i < 10000; i++) {
            System.out.println("hello world! endless loop!");
        }
    }
}

分析点击抽样器->CPU->查看CPU样例,发现endlessLoop()方法最耗CPU(这里有2个方法 loop和endlessLoop)

查看线程cpu耗时,发现main线程最耗时,点击增量,可以从此刻观察,cpu耗时的增长速率

查看线程dump,主要观察main线程,发现main线程当前状态下一直在执行CpuTest.endlessLoop(CpuTest.java:14),这里可以定位问题位置,同时细心的童鞋可以观察看后面执行System.out.println("");方法是要先加锁的。

截图一段,我生产服务器(tomcat+springmvc)main线程的情况,其实只想说明web项目启动的main方法在中间件里。

补充

VisualVm只能定位JVM的cpu情况,但是生产主机上不光是Java程序,这时我们要采取另外的方案。

1.看监控数据是否正常,cpu,mem。

CPU占用1.5左右(100-98.0id) 内存占用50%(435/100*100%=43.5%) 阿里云监控内存大小转成实际占用内存大小,类似windows ,平均负载 0.1 差不多,其他几个参数,这里暂不介绍。

2.假设异常,找到异常的PID。

这里推荐htop(清晰进程,线程,命令行,排序支持鼠标双击,过滤,kill程序,标记某个线程或者进程,安装apt-get install htop)

如果你没有服务器上安装软件的权限的话,就老老实实用top。通过top命令(默认3秒刷新,回车空格手动刷新,top -d 5 5秒刷新,也可以进入top后输入d设置刷新时间,top -p 4360 监控指定进程),然后按X ,默认按照CPU%排序,查看系统运行情况,如果想强制按CPU 降序,则输入大写P,如果强制按内存降序,则输入大写M(top命令是交互式的)。

解读
1).现在系统时间 10:18.44 ,系统一直运行了 131天16小时51分,当前有1个用户登录系统(相同账号也算不同用户),平均负载分别为0.00,0.01,0.05(分别为1分钟,5分钟,15分钟的负载情况,load average是每隔5秒钟检查一次活跃的进程数,用特定的算法得到的数值,然后除以逻辑CPU数量,如果负载持续大于cpu个数,则表明负载过高) 。大概可以看出系统负载很低,运行状态健康。

2).当前一共有103个进程,处于运行的有2个,处于休眠状态的有101个,处于停止状态的有0个,处于僵尸状态的有0个。大概可以看出系统进程总数较少,环境比较单纯,运行中的进程不多。

3).0.3 us 用户空间占用CPU 0.3%,0.7 sy内核占用CPU 0.7%,0.0 ni改变过优先级的进程占用CPU的百分比,98.0 id空闲CPU的百分比为98.0,0.3 waIO等待所占用CPU的百分比为0.3,0.3 hi硬中断(Hardware IRQ)占用CPU的百分比为0.3(外设给CPU的异步信号(中断),例如:网卡收到数据包),0.0 si软中断占用cpu的百分比为0(软件本身给操作系统内核的中断信号,通常由硬中断处理程序对操作系统内核的中断),0.3 st 虚拟机被hypervisor偷去CPU的时间。

4).KiB Mem 代表内存占用,1016272 total 内存总的大小1g(以kb为单位),941492 used 使用中的内存总量为0.9g,74780 free空闲内存总量为74m(吓一跳吧,才74M,这个不是实际剩余的内内存大小)115900 buffers缓存的内存量为115m,389836 cached cached大小380M。空闲内存总量只有74m,如果是windows去理解的话,此台服务器已经快挂了,实际内存大小等于74M+ buffers+cached = 580m(哈哈,够用,才占用一半呢), linux的内存管理和windows是不一样的,Linux会借用空闲的内存当作磁盘缓存, 磁盘数据缓存会让linux运行的更快,它永远不会从程序中拿出内存,它没有任何缺点,只是会混淆新手,如果你的应用程序需要更多的内存,他们会回收一部分用作磁盘数据缓存的物理内存,返回给应用程序,这个过程不需要启动交换,磁盘缓存(Disk caching)是不能禁用的, 但是可以释放磁盘缓存 1).只释放pagecache(文件缓存) echo 1 > /proc/sys/vm/drop_caches 2).释放dentries和inodes echo 2 > /proc/sys/vm/drop_caches 3).释放pagecache,dentries和inodes echo 3 > /proc/sys/vm/drop_caches
具体原理可以查看linuxatemyram

我们利用free 进一步证实上面的内容


1).shared代表被线程共享的内存大小(大多数已经舍弃掉),buffer用来给块设备做缓存(记录文件系统的metadata和tracking in flight pages),cache缓存文件(第二次打开就很快),linux系统cached比较大,cached大小390m,buffers为110m。

2).435288代表 -buffers/cache (应用程序实际使用330m内存) 580984 代表 +buffers/cache(实际剩余内存大小580m)。

3).重要等式 total = used + free used(-buffers/cache) = used(Mem)-buffers(Mem)-cached(Mem) free(+buffers/cache) = free(Mem) + buffers(Mem) +cached(Mem)

4).free命令的值是从 /proc/meminfo 文件里读到的。

5).Swap 交换区 总共大小 0 kb,已经使用0kb,释放了0Kb,如果swap used > 0,则可以说明系统内存瓶颈了

6).内存总和 free -t 总和等于total(Mem) + total(Swap)。

7).一般常用命令 free -s 3 每3秒观察一次内存使用情况。

占用cpu最高的进程是java,PID为28628,USER进程所有者root,PR优先级20,NI为0(负值优先级高,正值优先级低,PR=NI+20) ,VIRT进程使用虚拟内存1G(java进程最高只能占用到1G),RES进程实际使用的物理内存270M(不包含swap和shared),SHR共享内存大小5M,S进程状态随眠状态(S睡眠,R运行,T停止或被跟踪,Z僵尸,D不可中断睡眠态),%CPU占用CPU 0.7%,%MEM占用内存27%,TIME+进程使用cpu的时间54分40.12秒。总体看java进程运行良好。

3.这里假设PID为16368的进程占用CPU比较高(因为做了多次实验,所以PID没办法和上面的28628保持一致),
先用ps -ef | grep java,也可以用htop filter java,也可以jps -v 找到 java的进程ID 16368(只能查看当前用户的java pid,不太建议使用,-v显示详细信息,也可以不加)


?代表终端设备未知,Sl代表休眠状态多线程

Linux通过进程查看线程的方法 1).htop按t(显示进程线程嵌套关系)和H(显示线程) ,然后F4过滤进程名。2).ps -eLf | grep java(快照,带线程命令,e是显示全部进程,L是显示线程,f全格式输出) 3).pstree -p <pid>(显示进程树,不加pid显示所有) 4).top -Hp <pid> (实时) 5).ps -T -p <pid>(快照) 推荐程度按数字从小到大

4.利用jstack 16368 > 2016-1-21.tdump (jstack是jdk自带的生成java stack和native stack的工具) 把threaddump输出到文件里,这里假设PID为16369(一般比进程号+1的是main线程,如果存在main线程貌似最先分配)的线程占用CPU最高,将其ID转成16进制0x3ff1(使用命令 printf "%x\n" 16369 输出3ff1)。

5.搜索threaddump文件,nid= 0x3ff1的线程堆栈,通过堆栈信息,就可以定位到占用CPU最高的代码地方(这里是正常的)。

好啦,CPU冲高问题排查完毕,有了这样的知识体系,以后完全可以举一反三,coder君同时附上其他常用配合命令。

  • 每隔一秒显示新生代,老年代,持久代垃圾回收情况

jstat -gcutil 16368 1000ms 参考jstat -help,也可以查看远程主机的GC情况,需要远程主机开启jstatd服务。

  • 观察堆内存情况

jmap -dump:format=b,file=2016-1-21.mdump 16368 生成堆dump,放到mat或者vvm进行分析,上篇分析OOM有讲到。

  • 查看jvm系统参数

jinfo 16368

参考资料


感谢您的耐心阅读,如果您发现文章中有一些没表述清楚的,或者是不对的地方,请给我留言,你的鼓励是作者写作最大的动力,
如果您认为本文质量不错,读后觉得收获很大,不妨小额赞助我一下,让我更有动力继续写出高质量的文章。

  • 支付宝

  • 微信

作 者 : @mousycoder

原文出处 : http://mousycoder.com/2016/02...

创作时间:2016-2-14

更新时间:2016-2-14


mousycoder
5.4k 声望819 粉丝