java程序cpu飙高定位

1 定位过程

CPU飙升问题定位的一般步骤是:

  1. 首先通过top指令查看当前占用CPU较高的进程PID
  2. 查看当前进程消耗资源的线程PID:top -Hp PID
  3. 通过print命令将线程PID转为16进制,根据该16进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置
  4. 通过jstack命令,查看栈信息,定位到线程对应的具体代码
  5. 分析代码解决问题

2 常见原因及解决方案

  1. 如果是空循环,或者空自旋。

    处理方式:可以使用Thread.sleep或者加锁,让线程适当的阻塞。

  2. 在循环的代码逻辑中,创建大量的新对象导致频繁GC。比如,从mysql查出了大量的数据,比如100W以上等等。

    处理方式:可以减少对象的创建数量,或者,可以考虑使用 对象池。

  3. 其他的一些造成CPU飙升的场景,比如 selector空轮训导致CPU飙升 。

    处理方式:参考Netty源码,无效的事件查询到了一定的次数,进行 selector 重建。

3 正式案例

3.1 采用top命令定位进程

登录服务器,执行top命令,查看CPU占用情况,找到进程的pid

top

在这里插入图片描述

很容易发现,PID为29706的java进程的CPU飙升到700%多,且一直降不下来,很显然出现了问题。

3.2 使用top -Hp命令定位线程

使用 top -Hp <pid> 命令(为Java进程的id号)查看该Java进程内所有线程的资源占用情况

tip: 按shft+p按照cpu占用进行排序,按shift+m按照内存占用进行排序

top -Hp 29706

在这里插入图片描述

不难发现,多个线程的CPU占用达到了90%多。我们挑选线程号为30309的线程继续分析

3.3 使用jstack命令定位代码

3.3.1 线程号转换为16进制

可以使用 printf “%x\n” 命令(tid指线程的id号)将以上10进制的线程号转换为16进制

printf "%x\n"

在这里插入图片描述

转换后的结果分别为7665,由于导出的线程快照中线程的nid是16进制的,而16进制以0x开头,所以对应的16进制的线程号nid为0x7665

3.3.2 采用jstack命令导出线程快照

通过使用jdk自带命令jstack获取该java进程的线程快照并输入到文件中

注意:open jdk不带该命令|没有注册环境变量的,需要对应的java目录执行jstack命令

 jstack -l 进程ID > ./jstack_result.txt 

命令(为Java进程的id号)来获取线程快照结果并输入到指定文件

jstack -l 29706 > ./jstack_result.txt

3.3.3 根据线程号定位具体代码

在jstack_result.txt 文件中根据线程好nid搜索对应的线程描述

cat jstack_result.txt |grep -A 100  7665

在这里插入图片描述

3.4 分析代码解决问题

下面是ImageConverter.run()方法中的部分核心代码。

逻辑说明:

/存储minicap的socket连接返回的数据   (改用消息队列存储读到的流数据) ,设置阻塞队列长度,防止出现内存溢出
//全局变量
private BlockingQueue<byte[]> dataQueue = new LinkedBlockingQueue<byte[]>(100000);
//消费线程
@Override
public void run() {
    //long start = System.currentTimeMillis();
    while (isRunning) {
        //分析这里从LinkedBlockingQueue
        if (dataQueue.isEmpty()) {
            continue;
        }
        byte[] buffer = device.getMinicap().dataQueue.poll();
       int len = buffer.length;
}

在while循环中,不断读取堵塞队列dataQueue中的数据,如果数据为空,则执行continue进行下一次循环。

如果不为空,则通过poll()方法读取数据,做相关逻辑处理。

初看这段代码好像每什么问题,但是如果dataQueue对象长期为空的话,这里就会一直空循环,导致CPU飙升。

那么如果解决呢?

//取出队列中的头部元素,如果队列为空则调用此方法的线程被阻塞等待,直到有元素能被取出,如果等待过程被中断则抛出InterruptedException
E take() throws InterruptedException;
//取出队列中的头部元素,如果队列为空返回null
E poll();

这两种取值的API,显然take方法更时候这里的场景。

代码修改为:

while (isRunning) {
   /* if (device.getMinicap().dataQueue.isEmpty()) {
        continue;
    }*/
    byte[] buffer = new byte[0];
    try {
        buffer = device.getMinicap().dataQueue.take();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
……
}

重启项目后,测试发现项目运行稳定,对应项目进程的CPU消耗占比不到10%

本文案例取之网上,公司代码敏感

4 部分脚本代码

dumpapp(){
  echo "应用dump:=============start========================"
  is_exist
  if [ $? -eq 1 ];then
    echo "${appName} is not running"
    exit 0
  fi
  echo "${appName} Process ID is : $pid"
  echo "${appName} Process ID is : $pid" > ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  #CUP top cupNum的数据写入临时文件
  ps p ${pid} -L -o pcpu,pid,tid,time | sort -f -k4 | tail -n -${cupNum} | grep -E ':'> ${logsPath}/temp.txt
  echo "   "
  echo "CPU TOP ${cupNum} :"
  echo "CPU TOP ${cupNum} :" >> ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  cat ${logsPath}/temp.txt
  cat ${logsPath}/temp.txt >> ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  echo "   "

  processId=`awk '{print $3}' ${logsPath}/temp.txt`
  echo "${processId}" > ${logsPath}/temp.txt

  while read LINE
  do
    tempId=`printf "%x" ${LINE}`
    echo "线程${LINE}对应的十六进制编码是:${tempId}"
    echo "线程${LINE}对应的十六进制编码是:${tempId}" >> ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  done < ${logsPath}/temp.txt

  rm -rf ${logsPath}/${pid}.log
  rm -rf ${logsPath}/${pid}.hprof
  ${jdkPath}/jstack -l ${pid} > ${logsPath}/${appName}_${pid}_${caijiTime}.log
  ${jdkPath}/jmap -dump:format=b,file=${logsPath}/${appName}_${pid}_${caijiTime}.hprof ${pid}
  echo "Heap dump file Completion"
  echo "   "

  echo "log文件路径:${logsPath}/${appName}_${pid}_${caijiTime}.log"
  echo "hprof文件路径:${logsPath}/${appName}_${pid}_${caijiTime}.hprof"
  echo "log文件路径:${logsPath}/${appName}_${pid}_${caijiTime}.log" >> ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  echo "hprof文件路径:${logsPath}/${appName}_${pid}_${caijiTime}.hprof" >> ${logsPath}/${appName}_${pid}_${caijiTime}.txt
  echo "应用dump:=============end========================"
  
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java应用程序CPU过高内存不高的原因有以下几个可能: 1. 程序中存在大量的计算密集型操作或循环。Java是一种高级编程语言,它的执行速度相对较慢。如果应用程序中存在很多循环或者大量的复杂计算,就容易导致CPU占用率高。 2. 程序中存在死循环或者无限递归调用。死循环或者无限递归调用会导致程序陷入无限循环中,CPU会一直占用在该循环中,从而导致CPU占用率过高,而内存占用率不高。 3. 使用了不合理或者低效的算法。如果应用程序中使用了不合理或者低效的算法,就会导致CPU占用率高。这种情况下,可以尝试使用更加高效的算法或者数据结构来优化程序的性能。 4. 多线程使用不当。Java是支持多线程编程的语言,如果在程序中使用了大量的线程,并且这些线程之间存在竞争条件或者同步问题,就可能导致CPU占用率高。在使用多线程时,需要合理地进行线程管理和资源分配,避免出现资源争用导致的CPU过高问题。 综上所述,Java应用程序CPU过高而内存不高的原因可能是程序中存在大量的计算密集型操作、死循环或者无限递归调用、使用了不合理或者低效的算法,或者多线程使用不当等。在解决这个问题时,可以尝试优化程序的算法、检查并修正死循环或者无限递归调用的问题、合理利用多线程等方式来降低CPU的占用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值