虚拟机故障监控处理工具:JConsole、jstack、jstat

JConsole

JConsole 是一个综合性图形界面监控工具,可以用曲线的形式监控各种数据,包括 MBean 中的属性值。

在这里插入图片描述

内存监控

“内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存的变化趋势。

举例说明一下:

 public class CommonMistakesApplication {
    public static void main(String[] args) throws Exception {
        fillHeap(1200);
    }

    /*** 内存占位符对象,一个OOMObject大约占64KB */
    static class OOMObject {
        public byte[] placeholder = new byte[64 * 1024];
    }

    public static void fillHeap(int num) throws InterruptedException {
        List<OOMObject> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            // 稍作延时,令监视曲线的变化更加明显
            Thread.sleep(200);
            list.add(new OOMObject());
        }
        System.gc();
    }
}

这段代码的作用是以64KB/50ms的速度向Java堆中填充数据,一共填充1000次,使用JConsole 的“内存”页签进行监视,观察曲线和柱状指示图的变化。

设置虚拟机参数:

-Xms100m -Xmx100m -XX:+UseSerialGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails

整个堆内存趋势图

在这里插入图片描述

Eden Space 内存趋势图

在这里插入图片描述

Survivor Space 内存趋势图

在这里插入图片描述

Tenured Gen 内存趋势图

在这里插入图片描述

从上面的内存趋势图可以看出来,整个堆内存趋势图是一直平滑向上增长的,

而且在循环结束执行了System.gc()后,虽然整个新生代EdenSurvivor区都基本被清空了,但是代表老年代的柱状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。

为什么执行System.gc()方法后,老年代的对象没有被回收掉呢?

原因是因为,在执行System.gc()方法后,fillHeap()方法仍然没有退出,list 对象仍然处于作用域内,所以把System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。

回收结果如下图:

在这里插入图片描述
在这里插入图片描述

线程监控

“线程”页签的功能相当于可视化的jstack命令,遇到线程停顿的时候可以使用这个页签的功能进行分析。

线程长时间停顿的主要原因有等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等。

public class CommonMistakesApplication {
    public static void main(String[] args) throws Exception {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        bufferedReader.readLine();
        createBusyThread();
        bufferedReader.readLine();
        Object obj = new Object();
        createLockThread(obj);
    }
    /**
     * 测试死循环
     */
    public static void createBusyThread() {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getId());
            while (true) ;
        }, "testBusyThread");

        thread.start();
    }
    /**
     * 测试锁等待
     * @param obj
     */
    public static void createLockThread(final Object obj) {
        Thread thread = new Thread(() -> {
            synchronized (obj) {
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "testLockThread");
        thread.start();
    }
}

在这里插入图片描述

堆栈追踪显示BufferedReaderreadBytes()方法正在等待System.in的键盘输入,这时候线程为Runnable状态,Runnable状态的线程仍会被分配运行时间,但readBytes()方法检查到流没有更新就会立刻归还执行令牌给操作系统,这种等待只消耗很小的处理器资源。

在键盘上输入后,看 testBusyThread 线程的堆栈信息。

在这里插入图片描述

testBusyThread线程一直在执行空循环,从堆栈追踪中看到一直在CommonMistakesApplication代码的47行停留,47行的代码为while(true)
此时线程为Runnable状态,而且没有归还线程执行令牌的动作,所以会在空循环耗尽操作系统分配给它的执行时间,直到线程切换为止,这种等待会消耗大量的处理器资源。

在键盘上输入后,看testLockThread线程的堆栈信息。

在这里插入图片描述

testLockThread线程在等待lock对象的notify()notifyAll()方法的出现,线程这时候处于 WAITING状态,在重新唤醒前不会被分配执行时间。

testLockThread线程正处于正常的活锁等待中,只要lock对象的notify()notifyAll()方法被调用, 这个线程便能激活继续执行。

再看一个死锁等待的例子:

public class CommonMistakesApplication {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            synchronized (lock1) {
                try{
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {}
                synchronized (lock2) {
                    System.out.println("thread1 over");
                }
            }
        },"deadLock1").start();
        new Thread(() -> {
            synchronized (lock2) {
                try{
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {}
                synchronized (lock1) {
                    System.out.println("thread2 over");
                }
            }
        },"deadLock2").start();
    }
}

在这里插入图片描述
在这里插入图片描述

从死锁堆栈信息可以看出来,deadLock1 和 deadLock2 线程互相阻塞,都在等待获取对方所持有的锁。

jstat

如果没有条件使用图形界面(毕竟在 Linux 服务器上,我们主要使用命令行工具),又希望看到 GC 趋势的话,我们可以使用 jstat 工具。

通过 jstat -option 查看 jstat 有哪些操作:

在这里插入图片描述

  • -class : 显示 ClassLoad 的相关信息;
  • -compiler:显示 JIT 编译的相关信息;
  • -gc:显示和 gc 相关的堆信息;
  • -gccapacity:显示各个代的容量以及使用情况;
  • -gcmetacapacity:显示 Metaspace 的大小;
  • -gcnew:显示新生代信息;
  • -gcnewcapacity:显示新生代大小和使用情况;
  • -gcold:显示老年代和永久代的信息;
  • -gcoldcapacity :显示老年代的大小;
  • -gcutil:显示垃圾收集信息;
  • -gccause:显示垃圾回收的相关信息(同 -gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
  • -printcompilation:输出 JIT 编译的方法信息。

jstat 工具允许以固定的监控频次输出 JVM 的各种监控指标,比如使用 -gcutil 输出 GC 和内存占用汇总信息,每隔 3 秒输出一次,输出 100 次。

在这里插入图片描述

S0 表示 Survivor0 区占用百分比,S1 表示 Survivor1 区占用百分比,E 表示 Eden 区占用百分比,O 表示老年代占用百分比,M 表示元数据区占用百分比,YGC 表示年轻代回收次数,YGCT 表示年轻代回收耗时,FGC 表示老年代回收次数,FGCT 表示老年代回收耗时。

jstack

jstack是一种线程堆栈分析工具,最常用的功能就是使用 jstack pid 命令查看线程的堆栈信息,通常会结合 top -p pid -Hpidstat -p pid -t 一起查看具体线程的状态,也经常用来排查一些死锁的异常。

比如:上面那个死锁的代码,就可以用 jstack 打印出堆栈信息。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值