使用JDK命令排查故障实战案例详解

文章目录

        • 服务器CPU使用率达到99%
        • 程序发生了死锁
        • java应用大量消耗内存
            • 1.排查占内存大的对象
            • 2.排查是否创建太多线程
            • 3.是否内存分配过小
            • 4、查看gc情况
            • 5、代码问题

      学习了JDK命令,就是为了排查故障的,今天通过几个分类来了解在程序遇到故障的时候,如何使用JDK命令排查问题
如果对JDK命令不熟悉,可以先看一下我的前一篇文章

你了解JDK的命令行工具吗

服务器CPU使用率达到99%

      这里我们先写一个demo,写一个死循环
使用JDK命令排查故障实战案例详解_第1张图片
      然后启动改程序
在这里插入图片描述
      发现程序在这里已经开始运行了
      使用top查看,到底是什么导致故障,如下图:使用JDK命令排查故障实战案例详解_第2张图片
      清楚的看到有一个java进程cpu占用率100%

      我们另起一个终端,开始排查这个问题。

      首先,使用jps指令,看看有哪些虚拟机进程
在这里插入图片描述
      可以看到,我们运行的Test的进程ID是3667

      拿到了这个进程号之后,我们需要具体到线程,所以就需要使用
top -Hp 命令来查看到底是该进程的哪个线程出问题了
使用JDK命令排查故障实战案例详解_第3张图片
      可以看到在该进程中,ID为3368的线程占用cpu高,所以就排查该线程3368

      因为在后面的异常信息栈中,线程ID是以16进制来体现的,所以这块我们先转成16进制
使用c语言的语法,以16进制进行打印
在这里插入图片描述
      可以看到,16进制的3668是e54

      现在我们打印线程的异常信息栈
使用JDK命令排查故障实战案例详解_第4张图片
使用JDK命令排查故障实战案例详解_第5张图片
      找到了id为e54的线程,分析状态:
在这里插入图片描述
      该线程处于运行状态,在第3行发现了问题,所以应该是个死循环,这时我们打开代码进行分析
使用JDK命令排查故障实战案例详解_第6张图片
      确实是第3行在一直运行。

程序发生了死锁

先来写一个死锁demo

public class DeadLock {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        Thread thread = new Thread(() -> {
            Thread.currentThread().setName("a线程");
            a.foo(b);
        });
        Thread thread1 = new Thread(() -> {
            Thread.currentThread().setName("b线程");
            b.bar(a);
        });
        thread.start();
        thread1.start();
    }
}
class A {
    public synchronized void foo(B b) {
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了A的foo方法");
        System.out.println(Thread.currentThread().getName()+"准备调用b的last方法");
        b.last();
    }
    public synchronized void last() {
        System.out.println("调用了A类的last方法");
    }
}
class B {
    public synchronized void bar(A a) {
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了B的bar方法");
        System.out.println(Thread.currentThread().getName()+"准备调用a的last方法");
        a.last();
    }
    public synchronized void last(){
        System.out.println("调用了B类的last方法");
    }
}

      这个程序是一个死锁demo,先来简单解释一下:
      a线程调用了A类的foo方法,由于foo方法是一个同步方法,所以线程a对对象a加锁(哪个对象调用同步方法,就默认对该对象加锁),在a线程启动的同时,b线程也紧跟着启动了,b线程调用B类的bar方法,b线程对b对象加锁,这时b线程对b对象加锁。
      a线程开始准备调用b的last方法,确发现b对象已经被b线程加锁了,然后b线程准备调用a的last方法,发现a也被加锁了,然后都在等待对方释放锁,进入死锁状态。

      然后我们编译并运行该程序

使用JDK命令排查故障实战案例详解_第7张图片
      程序卡在了这里,我们开始排查问题。

      首先使用top命令来查看是什么原因
使用JDK命令排查故障实战案例详解_第8张图片
      发现没有占用cpu资源,也没有占用其他内存资源,大概率估计是死锁。
      直接打印异常信息栈,使用jstack命令
使用JDK命令排查故障实战案例详解_第9张图片
      因为我们对线程起了名字,所以排查的时候一眼就看到了a线程,b线程,仔细分析我标出的信息,如下图:
使用JDK命令排查故障实战案例详解_第10张图片
      这两个线程都处于阻塞状态,并且每个线程都持有了一把锁,并且都在等待对方持有的锁,这样就可以很明显的分析出来,这是一个死锁。

      具体在哪一行呢?
使用JDK命令排查故障实战案例详解_第11张图片
      一个在20行,一个在13行
      打开代码进行排查
使用JDK命令排查故障实战案例详解_第12张图片
      发现确实是在这两个方法进行调用的时候互相等待对方释放锁,所以出现了死锁。

java应用大量消耗内存

当出现Java应用大量消耗内存的时候,可能出现多种原因,可以从以下几种思路排查原因:

  1. 什么对象消耗内存最大
  2. 是否创建太多线程
  3. 新生的、老年代现在内存使用情况,确认是不是整体内存分配太小了
  4. 实时查看新生的、老年代内存使用情况,GC情况
  5. 代码层检查,是否有大对象创建?需要调用close()或dispose()来回收的资源是否回收了?

具体分析过程如下:

1.排查占内存大的对象

执行“jmap -histo:live 10765 | more”命令,以表格的方式显示存活对象的信息(已按对象所占bytes大小进行降序排列):
使用JDK命令排查故障实战案例详解_第13张图片
占内存最多的对象类型是org.apache.logging.log4j.core.async.RingBufferLogEvent,
总共18874368byte (18M),例子中属于正常使用。

tips:如果发现某类对象占用内存很大(几个G的大小),很可能是有问题的。
基本都是因为:该类对象创建太多,且一直未释放。比如:使用完IO资源后,未调用close()接口关闭、释放资源;又比如:消费者消费速度慢(或停止消费了),而生产者不断往队列中投递任务,导致队列中任务累积过多,任务对象占用内存太多而产生OutOfMemoryError

2.排查是否创建太多线程

执行“pstree -p 10765 | wc -l”,查看进程内的线程数
其中,10765为进程ID。
每个线程需要分配线程栈内存,创建线程太多,可能导致OutOfMemoryError。

3.是否内存分配过小

执行“jmap -heap 10765”,查看堆(新生代、老年代)内存分配大小及使用情况
使用JDK命令排查故障实战案例详解_第14张图片

4、查看gc情况

执行“jstat -gc 10765 1000”,查看各个区内存使用情况及GC情况
使用JDK命令排查故障实战案例详解_第15张图片
具体字段含义,通过“man jstat”寻求帮助。

主要查看:
EC:Eden区容量,EU:Eden区已使用量,OC:Old区容量,OU:Old区已使用量;
YGC:YongGC次数,YGCT:YongGC耗时,FGC:FullGC次数,FGCT:FullGC耗时;

5、代码问题

代码检查,重新检查代码中需要关闭资源的地方。

你可能感兴趣的:(JVM)