Java应用CPU占用过高问题排查

问题描述

测试环境CPU突然升高,日志查询无异常,可以通过使用jvm的调试工具定位问题。


问题分析

如果你的 Java 应用把 CPU 100% 打满,该怎么办呢?

JVM 调优,一般都是在负载压力测试情况下,对于单个节点对外提供服务出现瓶颈时,才会启用性能调优,而 JVM 调优只是其中的一部分。在进行 JVM 调优之前,我一般都会进行 CPU 消耗的分析、内存消耗的分析、磁盘 IO 的分析、网络 IO 的分析以及程序自身问题,在这些指标都正常的情况下,才会转向 JVM 的调优,主要是看内存大小分配是否合理,内存比例是否合理,结合系统特性对垃圾回收器选用是否合理,会对 JVM 进行相关阀值的更改(升带阀值、JIT[Just In Time]),通过打开 JVM 日志收集相关数据,往复这个过程

通常性能瓶颈的表象是资源消耗过多,外部处理系统的性能不足,或者资源消耗不多,但程序的响应速度却仍达不到要求。
资源主要消耗在 CPU、文件 IO、网络 IO、以及内存方面,机器的资源是有限的,当某资源消耗过多,通常会造成系统的响应速度慢
资源消耗不多,但程序的响应速度仍达不到要求的主要原因是程序代码运行效率不够高、没有充分使用资源或程序结构不合理

下面就结合实际情况来说明,对于 Java 应用,CPU 消耗过高的时,该如何处理?

CPU 消耗过高分析

在 Linux 中,CPU 主要用于中断、内核以及用户进程的任务处理,优先级为 中断>内核>用户进程

上下文切换

每个 CPU(或多核 CPU 中的每核 CPU)在同一时间只能执行一个线程,Linux 采用的抢占式调度,即为每个线程分配一定的执行时间,当到达执行时间、线程中有 IO 阻塞或高优先级线程要执行时,Linux 将切换执行的线程,在切换时要存储目前线程的执行状态,并恢复要执行的线程的状态,这个过程就称为上下文切换。
对于 Java 应用,典型的是在进行文件 IO 操作、网络 IO 操作、锁等待或线程 Sleep 时,当前线程会进入阻塞或休眠状态,从而出发上下文切换,上下文切换过多或造成内核占据较多的 CPU 使用,应用的响应速度下降

运行队列

每个 CPU 都维护了一个可运行的线程队列
运行队列值越大,意味着线程会消耗越长的时间才能完成
建议控制每个 CPU 核上的运行队列为 1~3 个

利用率

CPU 利用率为 CPU 在用户进程、内核、中断处理、IO等待以及空闲五个部分使用百分比,这五个值用来分析 CPU 消耗情况的关键指标。
建议用户进程的 CPU 消耗/内核的 CPU 消耗的比率在 65%~70%/30%~35% 左右

问题解决

思路:
定位java服务进程 --> 定位java线程 --> 定位代码块

定位java服务进程

一个服务器可能有多个java服务。通过top命令可查看是哪个服务cpu使用率较高。

top

top命令的输出可以分为两个部分:前半部分是系统统计信息,后半部分是进程信息。

前半部分信息:
第一行:系统当前时间、系统运行时间、当前登录用户数。load average 表示系统的平均负载,即任务队列的平均长度,分别表示 1 分钟、5 分钟、15 分钟
第二行:进程统计信息,分别有正在运行的进程数、睡眠进程数、停止的进程数、僵尸进程数
第三行:CPU统计信息,us 表示用户空间CPU占用率、sy 表示内核空间CPU占用率、ni 表示用户进程空间改变过优先级的进程 CPU 的占用率、id 表示空闲 CPU 占用率、wa 表示在进程执行过程中等待 IO 所占的百分比、hi 表示硬件中断所占的百分比、si 表示软件终端所占的百分比

几个概念:

RES:resident memory usage 常驻内存
(1)进程当前使用的内存大小,但不包括swap out
(2)包含其他进程的共享
(3)如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反
(4)关于库占用内存的情况,它只统计加载的库文件所占内存大小
RES = CODE + DATA

VIRT:virtual memory usage
(1)进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等
(2)假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量
VIRT = SWAP + RES

可在进入top试图后按 1,就会按核来显示消耗情况。

记住CPU使用率较高的PID号。

ps -aux |grep pid 
或者 
top -p pid 

补充:
当 CPU 消耗严重时,主要体现在 us、sy、wa 或 hi 的值变高,wa 的值时 IO 等待造成的;hi 的值变高主要因为硬件中断造成的,例如网卡接受数据频繁的状况;
对于 Java 应用而言,CPU 消耗严重主要体现在 us、sy 两个值上。
当 us 值过高时,表示运行的应用消耗了大部分的 CPU,在这种情况下,对于 Java 应用而言,最重要的为找到具体消耗 CPU 的线程所执行的代码。

定位java线程

每个java服务中有很多线程在执行,先定位到哪个线程CPU使用率较高,可以有下面3种方式:

  1. 通过ps命令
ps H -eo pid,tid,%cpu --sort=%cpu |grep <PID>
  1. 通过top命令
top -H -p <PID>
  1. 通过ps命令
ps -mp <PID> -o THREAD,tid,time

通过上面的方式的任意一种,找到CPU使用率较高的线程TID后,执行下面的命令:

printf "0x%x\n" <线程TID>

目的:将线程TID转换为16进制,为后面查找 jstack 日志做准备;

定位代码块

使用jstack生成虚拟机当前时刻的线程快照:

jstack -l <pid> >> jstackLog.out

目的:将当前堆栈信息保存为文件,通过16进制的TID查找问题所在的代码块;

搜索得到对应的线程信息,可直观的看到代码异常信息。



版权声明:本文为u014163312原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。