JVM08-虚拟机故障处理之可视化故障处理工具JConsole工具

简介: 上一篇我们介绍了JVM07-虚拟机故障处理命令行工具。这一篇将继续介绍虚拟机故障处理之可视化故障处理工具JConsole工具。这个工具我们可以在JDK的bin目录下找到。

前言

上一篇我们介绍了JVM07-虚拟机故障处理命令行工具。这一篇将继续介绍虚拟机故障处理之可视化故障处理工具JConsole工具。这个工具我们可以在JDK的bin目录下找到。

JConsole的介绍

JConsole是一款基于JMX(Java Management Extensions)的可视化监视、管理工具。它主要是通过JMX的MBean对系统进行信息收集和参数动态调整。JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中,典型的如中间件大多也是基于JMX来实现管理和监控的。

JConsole的使用

1. 启动JConsole

运行JDK/bin目录下的jconsole.exe就可以启动JConsole。JConsole启动之后会自动搜索出本机运行的所有虚拟机进程(只能监控运行在本虚拟机的进程),而不需要用户自己使用jps来查询,如图,有如下进程,双击选中JConsoleTest进程其中一个进程便可以进入主界面开始监控JConsoleTest进程的相关信息。同时JMX支持跨服务器的管理。

内存监控

"内存"页签的作用相当于可视化的jstat命令,用于监控被收集器管理的虚拟机内存(被收集器直接管理Java堆和被间接管理的方法区)的变化趋势。如下 JConsoleTest类循环创建OOMObject对象,每隔50ms创建一个,就相当于以 100KB/50ms的速度向Java堆中填充数据。一共填充1000次。我们可以进入内存 页签中观察内存变化趋势。

运行前的内存设置如下:设置堆内存最大为100m。

-Xms100m -Xmx100m  -XX:+UseSerialGC

上面我们只是指定了整个堆的内存,没有指定新生代的大小。那么整个新生代的堆内存大小是多少呢?看下图:

如上图,我们看到Eden区域的内存一直在平稳的增加,直到执行System.gc();之后才下降下来。

看左下角可以知道Eden区域的大小是27,328 KB,同时没有设置-XX:SurvivorRation,按照JVM默认的设置Eden与Survivor的比例为8:1,而新生代有两个Survivor区域。所以整个新生代的内存大小是27328KB*1.25=34160KB。

同时我们注意到在循环填充完数据之后,执行System.gc();之后,新生代的Eden和Survivor区域已使用内存明显下降,但是老年代的内存还处于高位,这是为啥呢?这是因为System.gc();是放在setOOMObject方法内部调用的,而在该方法内oomObjectList对象还是有效的,是不能被回收的。所以老年代还是处于高位。要是oomObjectList对象也能被回收,只需要将System.gc();的调用放到setOOMObject方法外部调用。这样才能使垃圾收集器可以收集老年代中的oomObjectList对象。

public class JConsoleTest {
    public static void main(String[] args) throws InterruptedException {
        setOOMObject(1000);
    }
    /**
     * 内存占位符对象,一个OOMObject大约占100KB。
     */
    static class OOMObject{
        private static final byte[] param = new byte[100 * 1024];
    }
    public static void setOOMObject(int num) throws InterruptedException {
        List<OOMObject> oomObjectList = new ArrayList<>();
        Thread.sleep(3000);
        for (int i = 0; i < num; i++) {
            System.out.println("*********第["+i+"]次设值");
            //休息50毫秒
            Thread.sleep(50);
            oomObjectList.add(new OOMObject());
        }
        System.gc();
    }
}

线程监控

说完了内存监控,我们接着来看看线程监控,如果说JConsole的"内存"页签相当于可视化的jstat命令的话,那"线程"页签的功能就相当于可视化的jstack命令了,遇到线程停顿的时候可以使用这个页签的功能进行分析。我们知道线程长时间停顿的主要原因有等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待等。下面用MonitoringTest类来模拟下等待外部资源、 死循环等待和锁等待等情况。

public class MonitoringTest {
    /**
     * 线程死循环演示
     */
    public static void createBusyThread() {
        new Thread(() -> {
            while (true) {
            }
        }, "testBusyThread").start();
    }
    /**
     * 线程锁等待演示
     * @param lock
     */
    public static void createLockThread(final Object lock) {
        new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "testLockThread").start();
    }
    public static void main(String[] args) throws IOException {
        //等待外部资源
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        bufferedReader.readLine();
        createBusyThread();
        bufferedReader.readLine();
        Object obj  = new Object();
        createLockThread(obj);
    }
}

运行MonitoringTest之后,在JConsole中观察其运行情况,首先我们在"线程"页签中选中main线程、堆栈追踪显示BufferedReader的readBytes()方法正在等待System.in的键盘输入。这时候线程为Runnable状态,Runnable状态的线程仍会被分配运行时间,但readBytes()方法检查到流没有更新就会立即归还令牌给操作系统,这种等待只消耗很小的处理器资源。如下图所示:

da4f216a141ab137585d2f0a5e5cd733_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

接着监控testBusyThread线程,如下图所示:testBusyThread线程一直在执行空循环,从堆栈追踪可以看到在MonitoringTest代码的第17行停留,第17行的代码为while(true)。这时候线程为Runable状态,而且没有归还线程执行令牌的动作,所以会空循环耗尽系统分配给它的执行时间,直到线程切换为止,这种等待会消耗大量的处理器资源。

c8cd95fb3b551a3b08b683438d8853c8_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

最后我们看看testLockThread线程在等待lock对象的notify()或者notifyAll()方法的出现,线程这时候处于WAITING状态,在重新唤醒之前不会被分配执行时间。同时会释放占用的锁对象。testLockThread线程正处于正常的活锁等待中,只要lock对象的notify()或notifyAll()方法被调用,这个线程便能激活继续执行。相关监控结果如下图所示:

50ef5a45252677f5242286d1445a8153_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

说完了活锁的情况,下面我们来看一个死锁的情况。如下JConsoleDeadLockTest类,在Runable的run方法中加了两把锁(synchronized),锁对象分别是 Integer.valueOf(a)和Integer.valueOf(b)。在main方法中定义两个线程,传入的a,b值相反。这种情况下就会出现死锁,原因是Integer.valueOf()方法处于减少对象创建次数和节省内存的考虑,会对数值为-128~127之间的Integer对象进行缓存,如果valueOf()方法传入的参数在这个范围内,就直接返回缓存中的对象。也就是说尽管调用了100次Integer.valueOf()方法,但一共只返回了两个不同的Integer对象,假如某个线程在两个synchronized块之间发生了一次线程切换,那就会出现线程A在等待了线程B持有的Integer.valueOf(1),而线程B又在等待线程A持有的Integer.valueOf(2),结果就发生了死锁。

public class JConsoleDeadLockTest {
    static class SyncAddRunner implements Runnable {
        int a, b;
        public SyncAddRunner(int a, int b) {
            this.a = a;
            this.b = b;
        }
        @Override
        public void run() {
            synchronized (Integer.valueOf(a)) {
                synchronized (Integer.valueOf(b)) {
                    System.out.println(a + b);
                }
            }
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new SyncAddRunner(1, 2),"线程一").start();
            new Thread(new SyncAddRunner(2, 1), "线程二").start();
        }
    }
}

我们接着看下在 JConsole中的监控情况。同样的选中线程 页签,然后,点击检查死锁 按钮,就可以看到 线程一和线程二发生了死锁。

8df22150ad6f682603ba52b4ad0250e1_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

总结

本文主要介绍了JConsole工具的使用场景,以及使用方法。JConsole是JDK自带的可视化监控工具,在实际的工作中我们可以用它来分析系统的运行状况。

参考

深入理解Java虚拟机(第3版)

相关文章
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
2950 4
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
429 27
|
数据挖掘 索引
服务器数据恢复—raid6阵列硬盘故障导致上层虚拟机不可用的数据恢复案例
一台由16块硬盘组成的raid6磁盘阵列。磁盘阵列中有一块硬盘因为物理故障掉线,导致服务器上层虚拟机无法正常使用,部分分区丢失,重启物理服务器后发现数据丢失。
|
存储 数据挖掘 虚拟化
vsan数据恢复—vsan缓存盘故障导致虚拟机磁盘文件丢失的数据恢复案例
VMware vsan架构采用2+1模式。每台设备只有一个磁盘组(7+1),缓存盘的大小为240GB,容量盘的大小为1.2TB。 由于其中一台主机(0号组设备)的缓存盘出现故障,导致VMware虚拟化环境中搭建的2台虚拟机的磁盘文件(vmdk)丢失。
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
407 28
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
8月前
|
Oracle 关系型数据库 虚拟化
在VMware的Win10虚拟机中安装使用ENSP
本文介绍了在Windows 10虚拟机上安装ENSP及相关软件的全过程,包括VirtualBox、WinPcap、Wireshark、VLC和ENSP的安装步骤,并提供图文演示,帮助用户顺利完成配置与测试。
2143 134
|
7月前
|
Linux 虚拟化 iOS开发
VMware Remote Console 13.0.1 for macOS, Linux, Windows - vSphere 虚拟机控制台的桌面客户端
VMware Remote Console 13.0.1 for macOS, Linux, Windows - vSphere 虚拟机控制台的桌面客户端
1627 0
VMware Remote Console 13.0.1 for macOS, Linux, Windows - vSphere 虚拟机控制台的桌面客户端
|
7月前
|
Linux 虚拟化 iOS开发
VMware Fusion 25H2 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式
VMware Fusion 25H2 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式
1622 0
VMware Fusion 25H2 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式