JVM-11虚拟机性能监控与故障处理工具之【JDK的可视化工具-JConsole】

简介: JVM-11虚拟机性能监控与故障处理工具之【JDK的可视化工具-JConsole】

思维导图

20180807073036703.png

概述


JVM-10虚拟机性能监控与故障处理工具之【命令行】我们接触了JDK提供的命令行工具,JDK还为我们提供了两个功能强大的可视化工具:JConsole和VisualVM。


JConsole在JDK1.5版本供就已经提供,而VisualVM是在JDK1.6 Update7中才首次发布。现在已经成为Oracle主力推动的多合一故障处理工具。


JConsole: Java监视与管理平台


JConsole ( Java Monitoring and Management Console)是一种基于JMX的可视化监控、管理工具。 它管理部分的功能是针对JMX MBean进行管理。 由于MBean可以在代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问。


所以我们这里重点介绍JConsole监视部分的功能。


启动jconsole


通过JDK安装目录下的bin目录“jconsole.exe”启动JConsole后,会自动列出本机运行的所有虚拟机进程,无需通过jps来查询了。除了本地进程,也可以使用“远程进程”的共鞥来连接远程服务器。


image.png


内存监控示例

VM ARGS

-Xms100m -Xmx100m -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails


image.png

堆最小内存100M 最大内存100M, 即为不可扩展。 使用Serial垃圾收集器。同时为了方便和图形化界面展示的数据比对,我们打印GC日志以及堆信息


代码

package com.artisan.gc;
import java.util.ArrayList;
import java.util.List;
public class OOMObject {
  // 内存占位符对象 一个object 大约占用64kb
  public byte[] object = new byte[64 * 1024];
  public static void fillData2Heap(int number) throws InterruptedException {
    List<OOMObject> objectList = new ArrayList<OOMObject>();
    for (int i = 0; i < number; i++) {
      // 休眠50毫秒,使曲线变化的更加明显
      Thread.sleep(50);
      objectList.add(new OOMObject());
    }
    System.gc();
  }
  public static void main(String[] args) throws InterruptedException {
    fillData2Heap(1000);
    // 为了让JConsole上的内存变化体现在工具上,让主线程不要立刻退出,休眠10S
    Thread.sleep(10 * 1000);
  }
}


JConsole监控展示及说明

运行程序,执行JConsole.exe .


20180807144109320.png



上图为整个堆内存的使用情况,可以看到随着程序的运行,整个堆中内存使用是一个平滑向上增长的曲线(平的部分为Thread.sleep(10 * 1000)造成的,)程序运行结束退出后,内存归为0。

然后我们我们来看下Eden区域的曲线变化情况,如下图。


20180807151907864.png

可以很明显的看到发生了2次 Minor GC. 和输出的GC日志信息一致。


20180807151352908.png

同时在这里也可以看出来

2018080715205951.png


新生代主要是复制算法,这里的统计信息也是 2次收集。


扩展问题

没有指定-Xmn,如何确定新生代和Eden的大小

2018080714555227.png


可以从如下图中查看每个分区的内存分配情况


20180807152247951.png


当然也可以粗略计算出来


默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。


堆内存为100M,采用默认的–XX:NewRatio,可以推算出 新生代占用1/3 , 33M .


没有指定-XX:SurvivorRatio , 默认为8。


新生代又分为 Eden区、SurvivorFrom、SurvivorTo三个区


默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。


JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。


因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间,只有10%的内存被“浪费”,最大限度的节约资源。


Eden内存区的大小为 (33M * 9/10 ) 新生代可用的空间 * 8/9 (Eden占用新生代的比例) = 27033.6KB 。 和 图中显示的27328 KB 接近。


为何老年代的柱状图信息仍显示峰值状态,如何调整代码回收该区域

20180807153248664.png


可以看到老年代仍然占据很大的内存空间,是因为 虽然执行了System.gc()。 但是是在fillData2Heap方法内执行的。 在方法内,List<OOMObject>是存活的,该方法还没退出,gc无法回收。


只要在该方法外执行GC就可以将老年代的内存回收。 我们来实践下

20180807153749417.png


启动JConsole


20180807153943741.png

线程监控示例


上面的“内存”可以看做是 jstat命令的话,“线程”页签就可相当于jstack命令, 遇到线程停顿时可以使用这个页签进行监控分析。 用于定位线程长时间停顿的原因,比如等待外部资源(数据库连接、网络资源、设备资源等),锁等待(活锁和死锁)、死循环。


活锁等待示例


package com.artisan.gc;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ThreadWaitTest {
  /**
   * 
   * 
   * @Title: createBusyThread
   * 
   * @Description: 线程死循环演示
   * 
   * 
   * @return: void
   */
  public static void createBusyThread() {
    Thread thread  = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          // do something
          // System.out.println("busyThread running");
        }
      }
    }, "busyThread");
    // 启动线程
    thread.start();
  }
  /**
   * 
   * 
   * @Title: createLockThread
   * 
   * @Description: 线程锁等待演示
   * 
   * @param lock
   * 
   * @return: void
   */
  public static void createLockThread(final Object lock) {
    Thread  thread = new Thread(new Runnable() {
      @Override
      public void run() {
        synchronized (lock) {
          try {
            lock.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }, "lockThread");
    thread.start();
  }
  public static void main(String[] args) throws IOException {
    System.out.println("请输入");
    BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in));
    bReader.readLine();
    createBusyThread();
    bReader.readLine();
    Object object = new Object();
    createLockThread(object);
  }
}


运行程序,启动 jconsole, 选择该进程 ,选择 “线程”页签,

首先查看main线程


20180807185215207.png


堆栈追踪显示


20180807185830951.png


BufferedReader在readBytes方法中等待System.in的键盘输入,这时线程状态为Runnable状态,Runnable状态的线程会被分配运行时间,但readBytes方法检测到流没有更新会立刻归还执行令牌,这种等待只消耗很小的cpu资源。


输入内容后,接着监控 “busyThread”线程


20180807185653680.png


可以看出一直在ThreadWaitTest.java的 第24行停留 ,我们查看ThreadWaitTest的24行内容


20180807190208367.png

while (true)

这时线程为Runnable状态,而且没有归还线程执行令牌的动作,会在空循环上用尽全部的执行时间,直到线程切换,这种等待会消耗较多的CPU资源。

接着控制台再输入内容


20180807190742278.png

继续回到jconsole查看 “lockThread”线程


20180807190829560.png


lockThread线程正在处于正常的活锁等待,只要lock对象的notify或者notifyAll方法被调用,这个线程便可以继续执行。


死锁等待示例

package com.artisan.gc;
public class ThreadLockTest {
  static class ThreadDemo implements Runnable {
    int a, b;
    public ThreadDemo(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 < 200; i++) {
      new Thread(new ThreadDemo(1, 2)).start();
      new Thread(new ThreadDemo(2, 1)).start();
    }
  }
}


先分析下造成死锁的原因: Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,【-128,127】之间的数字会被缓存,当valueOf()方法传入参数在这个范围内的时候,将直接返回缓存中的对象。 也就是说上述代码中虽然调用了200次的Integer.valueOf()方法,但是入参为1和2,一共就返回了2个不同的对象。 假如在某个线程的两个synchronized块之间发生了一次线程切换,那么就会出现线程A等着被线程B持有的Integer.valueOf(1),线程B等着被线程A持有的Integer.valueOf(2),造成死锁。


点击“检测到死锁”按钮,如果 有死锁将会出现一个新的“死锁”页签。


201808071917393.png


image.png


image.png


可以看到Thread-27和Thread-28互相阻塞,已经没有等到锁释放的希望了。

相关文章
|
12月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
2717 4
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
416 27
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
数据挖掘 索引
服务器数据恢复—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应用性能的思路与方法。 ####
389 28
|
Arthas Prometheus 监控
监控堆外使用JVM工具
监控堆外使用JVM工具
505 7
|
监控 Java 开发者
Java虚拟机(JVM)深度优化指南####
本文深入探讨了Java虚拟机(JVM)的工作原理及其性能优化策略,旨在帮助开发者通过理解JVM的内部机制来提升Java应用的运行效率。不同于传统的技术教程,本文采用案例分析与实战技巧相结合的方式,为读者揭示JVM调优的艺术。 ####
482 8
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####