• java 一次CPU占用过高问题的排查及解决


    转发https://www.cnblogs.com/xxj0316/archive/2018/08/09/9448987.html

    最近一段时间  某台服务器上的一个应用总是隔一段时间就自己挂掉      用top看了看  从重新部署应用开始没有多长时间CPU占用上升得很快  

    排查步骤

    1.使用top 定位到占用CPU高的进程PID

      top 

    2.通过ps aux | grep PID命令

      获取线程信息,并找到占用CPU高的线程

      ps -mp pid -o THREAD,tid,time | sort -rn 

    3.将需要的线程ID转换为16进制格式

      printf "%x " tid

    4.打印线程的堆栈信息  到了这一步具体看堆栈的日志来定位问题了

      jstack pid |grep tid -A 30

    ----------------------------------------------------------------------------  华丽的分割线  ------------------------------------------------------------------------------------------------------------------

    top     可以看出PID  733进程 的占用CPU  172%

     
     
    查找进程733下的线程  可以看到TID 线程775占用了96%且持有了很长时间  其实到这一步基本上能猜测到应该是    肯定是那段代码发生了死循环

    ps -mp 733 -o THREAD,tid,time | sort -rn

     

    线程ID转换为16进制格式

    printf "%x " 775

     查看java  的堆栈信息

    jstack 733 |grep 307 -A 30

    显然是 SmsQueueServiceImpl 中的produceMissSms   和 consumeMissSms  方法有问题

    一下为精简的部分代码

    复制代码
    /**
    * Created by dongxc on 2015/7/7. 通知消息队列
    */
    @Service("smsQueueService")
    public class SmsQueueServiceImpl {
        // 生产异常队列方法
        public void produceMissSms(SmsLogDo smsLogDo) {
            /*
             * try{ String key = EnumRedisPrefix.SMS_QUEUE_MISS_DEAL.getValue(); boolean result = redisService.lpush(key,
             * smsLogDo, 0); if(result==false){ logger.error("通知消息异常队列生产消息返回失败!"+smsLogDo.getId()); } }catch(Exception e){
             * logger.error("通知消息异常队列生产消息失败!", e); }
             */
        }
    
        // 消费异常队列方法
        public SmsLogDo consumeMissSms() {
            try {
                String destKey = EnumRedisPrefix.SMS_QUEUE_MISS_DEAL.getValue();
                SmsLogDo smsLogDo = new SmsLogDo();
                Object obj = null;
                if (obj == null) {
                    return null;
                } else {
                    smsLogDo = (SmsLogDo) obj;
                }
                return smsLogDo;
            } catch (Exception e) {
                logger.error("通知消息队列消费方法失败!", e);
                return null;
            }
        }
    }
    复制代码

    从很有年代感的垃圾代码来看  这两个方法并没有什么问题  继续往调用这两个方法的上层排查

    复制代码
    /**
     * Created by dongxc on 2015/7/7.
     * 消息通知监控线程
     */
    @Service("smsMonitorComsumer")
    public class SmsMonitorComsumerImpl {
    
        @Autowired
        private SmsQueueServiceImpl smsQueueService;
        
        //取队列里的任务消费
        @Transactional(propagation= Propagation.NOT_SUPPORTED)
        public void run() {
    
        while (true) {
                try {
                    SmsLogDo smsLogDo = smsQueueService.consumeMissSms();
                    Boolean result = false;
                    if(smsLogDo!=null){
                        long diff = (new Date()).getTime() - smsLogDo.getSendtime().getTime() ;
                        long min  = diff%(1000*24*60*60)%(1000*60*60)/(1000*60);//计算差多少分钟
                        if(min>5){
                            result = true;
                        }
                    }
                    if(result){
                        smsQueueService.produceSms(smsLogDo);
                    }else{
                        smsQueueService.produceMissSms(smsLogDo);
                    }
                } catch (Exception ex) {
                    try{
                        Thread.sleep(3000);
                    }catch(Exception e){
                        //logger.error("发送站内信息短信时线程执行失败2!", e);
                    }
                }
            }
    
    
    
        }
    }
    复制代码

    很显然  这里有一个while(true)  无数个草泥马策马奔腾           ps:垃圾代码看多了, 我已经不愤怒了. 

     基本定位到问题了      while里面完全是没有用的代码

    继续往上层看谁来调用

    复制代码
    /**
     * Created by dongxc on 2015/7/7.
     * 通知消息队列
     */
    @Service("smsLogRunThread")
    public class SmsLogRunThreadImpl {
        public int flag;
        @Autowired
        private SmsLogConsumerImpl smsLogConsumer;
        @Autowired
        private SmsMonitorComsumerImpl smsMonitorComsumer;
    
        @PostConstruct
        public void init() {
            
            
            if(ip!=""&&host!=""&&ip.equals(host)){
                Thread thread = new Thread(){
                    public void run() {
                        smsLogConsumer.run();
                    }
                };
                thread.start();
                Thread thread1 = new Thread(){
                    public void run() {
                        smsMonitorComsumer.run();
                    }
                };
                thread1.start();
            }
    
            
        }
    }
    复制代码

    在应用一启动的时候   spring初始化的就会执行这一段处理丢失消息的代码   然后这段死循环代码  没有任何作用    

    解决方法   即   注释掉whlie(true)这一段代码

     重新部署后 cpu占用就很正常了

    案例一下,其实之前也遇到过CPU占用很高的问题,  但是那次是  频繁的GC导致的

    其实排查问题 的过程中也是在不断的学习的过程 ! 先打个鸡血,我要继续搬砖了

  • 相关阅读:
    shell数组
    Apache HTTP Server 与 Tomcat 的三种连接方式介绍
    实现Java动态类载入机制
    Tomcat 阀
    MYSQL 常用命令
    MYSQL字符数字转换
    主题:MySQL数据库操作实战
    日本手机三大代理商的UA
    Java解析XML文档——dom解析xml (转载)
    MS sql server和mysql中update多条数据的例子
  • 原文地址:https://www.cnblogs.com/cheyunhua/p/14750094.html
Copyright © 2020-2023  润新知