美文网首页Bug追踪
《作死故障篇三》- 如何快速定位死循环

《作死故障篇三》- 如何快速定位死循环

作者: 逍遥无极 | 来源:发表于2018-05-12 12:23 被阅读76次

    项目发布后,发现机器的cpu飙升,load升高。在排查问题的过程中首先想到的应该是排查一下死锁、死循环。死锁与死循环都会导致cpu飙升(飙升的前提是多个线程都进入了死锁或者死循环,如果是一个线程进入死锁或者死循环状态那么cpu的使用率应该在100%左右)。本文主要讲解如何快速定位死循环,死锁定位原理一样。

    案例演示

    为了演示死循环,本文使用以后代码作为演示示例:

    package com.feng.home;
    
    public class EndLessLoopDemo {
    
        public static void main(String[] args) {
    
            for(int i=0; i<10; i--){
                System.out.println("endless loop !");
            }
        }
    }
    

    打包代码、上传至服务器
    在执行代码之前,首先看一下服务器的资源使用情况,使用top命令,并按1键,资源使用情况如下:


    image.png

    从图中可以看出,该服务器资源为24核,每个核的使用不超过10%
    接下来执行java命令,启动EndLessLoopDemo程序,命令如下:

    java -classpath home.jar com.feng.home.EndLessLoopDemo > /dev/null &
    

    执行命令后的再次查看系统资源的使用情况:


    image.png

    从上图中可以看出,EndLessLoopDemo的进程号为15752,cpu使用率一直维持在100.3%,再查看各个核的时候情况,可以很明显的看出其中一个核(上图中的cpu14)的使用率在66.9%,使用率非常高,如果我们只是实现了一个非常简单逻辑的需求,并没有耗cpu资源的操作后,我们应该要意识到,肯定是自己的程序出了问题。

    问题排查

    为了排查问题,我们应该将该进程的线程栈信息打印出来,查看忙碌的线程在做什么,使用jstack命令

    jstack 15752 > stack.info
    

    拿到线程栈信息之后,就可以查看该文件,由于线程较多,我们如何快速查找处于死循环的线程?试想一下如果程序发生了死循环,首先该线程的运行状态应该是Runnable状态(除非在循环中使用sleep, wait等操作);其次,程序应该会执行自己所写的代码,只要去搜索一下自己的包名即可,如果搜索到自己的包名,就可以查看该线程运行到哪一条语句,定位到代码中的位置,查看是否存在死循环。
    在该案例中,我们需要查找com.feng.home,执行命令:

    grep -B20 'com.feng.home' stack.info | more
    

    输出结果如下:

    "main" #1 prio=5 os_prio=0 tid=0x00007ff664008800 nid=0x3d89 runnable [0x00007ff66ddc0000]
       java.lang.Thread.State: RUNNABLE
            at java.io.FileOutputStream.writeBytes(Native Method)
            at java.io.FileOutputStream.write(FileOutputStream.java:326)
            at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
            at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
            - locked <0x00000003d3209700> (a java.io.BufferedOutputStream)
            at java.io.PrintStream.write(PrintStream.java:482)
            - locked <0x00000003d3208b50> (a java.io.PrintStream)
            at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
            at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
            at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
            - locked <0x00000003d3208b38> (a java.io.OutputStreamWriter)
            at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
            at java.io.PrintStream.write(PrintStream.java:527)
            - eliminated <0x00000003d3208b50> (a java.io.PrintStream)
            at java.io.PrintStream.print(PrintStream.java:669)
            at java.io.PrintStream.println(PrintStream.java:806)
            - locked <0x00000003d3208b50> (a java.io.PrintStream)
            at com.feng.home.EndLessLoopDemo.main(EndLessLoopDemo.java:8)
    

    从输出结果中我们可以看出,main线程正在执行EndLessLoopDemo类中的第8行代码,而第8行代码就是

    System.out.println("endless loop !");
    

    因为我们可以去查看该循环的条件是否存在问题,即定位到了死循环的位置。

    总结

    一个线程的死循环并不会打满一个cpu核,因为是时间片轮转,所以会导致其中某几个cpu核使用率存在明显的上升,但是该进程的cpu使用率肯定是在100%+(排除线程休眠、等待的情况)。
    如果n个线程执行到死循环语句块中,那么cpu使用率通常会在n*100%左右,因此在简单的逻辑需求中如果出现了cup飙升的情况,应该首先查找程序中是否存在死循环或者死锁(死锁的检测同样是在jstack输出中,查找Dead Lock即可)

    常见死循环的案例汇总

    • while + continue
        public void endLessLoop(boolean isDone){
    
            int retry = 0;
            while(retry < 3){
    
                if(isDone){
                    continue;
                }
                retry++;
            }
        }
    
    • while + try-catch
        public void endLessLoop(){
    
            int retry = 0;
            while(retry < 3){
    
                try{
                    Integer.parseInt("aaa");
                    retry++;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    

    因此要慎用while循环,如果该循环是for循环的话没有任何问题,使用while循环时一定要主要细节问题。

    欢迎扫描下方二维码,关注公众号,我们可以进行技术交流,共同成长

    qrcode_for_gh_5580beb3cba1_430.jpg

    相关文章

      网友评论

        本文标题:《作死故障篇三》- 如何快速定位死循环

        本文链接:https://www.haomeiwen.com/subject/znlmlftx.html