java自带可视化性能监控工具jconsole

JConsole 可视化工具介绍


一、 JConsole介绍
1.1 JConsole描述
Jconsole (Java Monitoring and Management Console),一种基于JMX的可视化监视、管理工具。

1.2 启动JConsole
点击JDK/bin 目录下面的jconsole.exe 即可启动
然后会自动自动搜索本机运行的所有虚拟机进程。
选择其中一个进程可开始进行监控


1.3JConsole基本介绍
JConsole 基本包括以下基本功能:概述、内存、线程、类、VM概要、MBean

运行下面的程序、然后使用JConsole进行监控;注意设置虚拟机参数

import java.util.ArrayList;
import java.util.List;

/**
 *  设置虚拟机参数:
 *  -Xms100M -Xms100m -XX:+UseSerialGC -XX:+PrintGCDetails
 */
public class JConsoleTool {

    static class OOMObject {
        public byte[] placeholder = new byte[64 * 1024];
    }

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

    public static void main(String[] args) throws Exception {
        fillHeap(1000);
        while(true){
            //让其一直运行着
        }

    }
}



1.3.1 内存监控
内存页签相对于可视化的jstat 命令,用于监视受收集器管理的虚拟机内存。

选项    描述
Eden Space 的大小    27328KB
已用    正在使用
已提交    27328KB
最大值    27328KB
copy 上的 0.120s(3收集)    新生代使用赋值算法(copy),0.120s,总共三次
MarkSweepCompact上的 0.037(1收集)    老年代使用标记清除整理,耗时0.037,总共一次
对应的GC日志。

[GC (Allocation Failure) [DefNew: 27277K->3392K(30720K), 0.0349173 secs] 27277K->14749K(99008K), 0.0350411 secs] [Times: user=0.03 sys=0.00, real=0.04 secs] 

[GC (Allocation Failure) [DefNew: 30691K->3378K(30720K), 0.0446635 secs] 42049K->39217K(99008K), 0.0447387 secs] [Times: user=0.03 sys=0.01, real=0.04 secs] 

[GC (Allocation Failure) [DefNew: 30679K->3372K(30720K), 0.0408609 secs] 66518K->64734K(99008K), 0.0409604 secs] [Times: user=0.02 sys=0.02, real=0.04 secs] 

[Full GC (System.gc()) [Tenured: 61362K->66352K(68288K), 0.0372192 secs] 67024K->66352K(99008K), [Metaspace: 9535K->9535K(1058816K)], 0.0373411 secs] [Times: user=0.05 sys=0.00, real=0.04 secs] 

1.3.2 线程监控
如果上面的“内存”页签相当于可视化的jstat命令的话,“线程”页签的功能相当于可视化的jstack命令,遇到线程停顿时可以使用这个页签进行监控分析。线程长时间停顿的主要原因主要有:等待外部资源(数据库连接、网络资源、设备资 
源等)、死循环、锁等待(活锁和死锁)

下面三个方法分别等待控制台输入、死循环演示、线程锁等待演示

/**
  * 等待控制台输入
  * @throws IOException
  */
 public static  void waitRerouceConnection () throws IOException {
     BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
     br.readLine();
 }
/**
 * 线程死循环演示
 */
 public static void createBusyThread() {
     Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
             while (true)   // 第41行
                 ;
         }
     }, "testBusyThread");
     thread.start();
 }

 /**
  * 线程锁等待演示
  */
 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();
                 }
             }
         }
     }, "testLockThread");
     thread.start();
 }



(二)线程死锁演示

这段代码开了200个线程去分别计算1+2以及2+1的值,其实for循环是可省略的,两个线程也可能会导致死锁,不过那样概率太小,需要尝试运行很多次才能看到效果。一般的话,带for循环的版本最多运行2~3次就会遇到线程死锁,程序无法结束。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,[-128,127]之间的数字会被缓存[3],当valueOf()方法传入参数在这个范围之内,将直接返回缓存中的对象。也就是说,代码中调用了200次Integer.valueOf()方法一共就只返回了两个不同的对象。假如在某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A等着被线程B持有的Integer.valueOf(1),线程B又等着被线程A持有的Integer.valueOf(2),结果出现大家都 
跑不下去的情景。

package com.jvm;

/**
 * 线程死锁验证
 */
public class JConsoleThreadLock {

    /**
     * 线程死锁等待演示
     */
    static class SynAddRunalbe implements Runnable {
        int a, b;
        public SynAddRunalbe(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 SynAddRunalbe(1, 2)).start();
            new Thread(new SynAddRunalbe(2, 1)).start();
        }
    }


}


结果描述:显示了线程Thread-53在等待一个被线程Thread-66持有Integer对象,而点击线程Thread-66则显示它也在等待一个Integer对象,被线程Thread-53持有,这样两个线程就互相卡住,都不存在等到锁释放的希望了

二、 VisualVM介绍
VisualVM(All-in-One Java Troubleshooting Tool);功能最强大的运行监视和故障处理程序

2.1 功能描述
显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
监视应用程序的CPU、GC、堆、方法区(1.7及以前),元空间(JDK1.8及以后)以及线程的信息(jstat、jstack)。
dump以及分析堆转储快照(jmap、jhat)。
方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。
离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照
2.2 使用教程
如何使用,直接查看官网和本书教程即可。

参看资料
VisualVM官网地址:帮助文档
BTrace 简要介绍
《深入理解java虚拟机》–周志明
 

 

1. 前言

  • 想验证你对 jvm 配的一些调优参数(比如 Xms、Xmx 等)有没有起作用吗?
  • 想不想实时监控你自定义的线程池的在实际运行时的线程个数、有没有死锁?
  • 应用出现 java.lang.OutOfMemoryError: Java heap space,你知道需要去调整 Xms、Xmx。想不想实时监控你的 Java 应用的堆内存使用情况,并根据峰值等数据设置最适合你的 Xms、Xmx 等参数?
  • 应用出现 java.lang.OutOfMemoryError: PermGen space,你知道需要去调整 XX:PermSize、XX:MaxPermSize。想不想找到你的应用的永久区 PermGen 的使用峰值,并根据其去设置合理的 XX:PermSize、XX:MaxPermSize 等参数?
  • 我们都知道,JVM 堆内存划分为年轻代和年老代。JVM 默认下的年老代与年轻代的比例(即 XX:NewRatio,这个名字容易让人产生混淆,即认为是年老代比年轻代)为 2(即把 JVM 堆内存平均分成了三份,年老大占用了两份,而年轻代占用一份。参考资料 Sun Java System Application Server Enterprise Edition 8.2 Performance Tuning Guide),这个比例并不适合所有情况,特别是当你的应用里局部变量远远大于全局变量,而且大量局部变量生命周期都很短的时候。如何根据应用实时的运行运行情况合理配置年轻代(Young Generation,即 Eden 区和两个 Survivor 区之和)和年老代(Old Generation,即 Tenured 区)的比例 XX:NewRatio 值?

Java 自带性能监控工具:监视和管理控制台 jconsole,它可以提供 Java 某个进程的内存、线程、类加载、jvm 概要以及 MBean 等的实时信息,也许能够对以上问题提供参考。

 

2. JVM 一些参数

在启动 jconsole 之前我们先来回顾一下 JVM 的一些主要参数:

  • -Xms 初始/最小堆内存大小
  • -Xmx 最大堆内存大小
  • -Xmn 年轻代大小
  • -XX:NewSize 年轻代大小
  • -XX:MaxNewSize 年轻代最大值
  • -XX:NewRatio 年老代与年轻代比值
  • -XX:MaxPermSize 持久代最大值
  • -XX:PermSize 持久代初始值

有些资料说,Xms、Xmx 设置的是 JVM 内存大小,是不对的,JVM 除了留给开发人员使用的堆内存之外还有非堆内存。

读者可能发现,有三种方式可以划分年轻代大小:-Xmn 方式、-XX:NewSize + -XX:MaxNewSize 方式、-XX:NewRatio 方式。三种都可以,优先级从高到低依次是 -XX:NewSize + -XX:MaxNewSize 方式、-Xmn 方式、-XX:NewRatio 方式,也就是说配置了前面优先级高的后面的优先级低的就被覆盖掉了。

 

3. 本机启用 jconsole 以监控 Java 进程

CMD 切换到 %JAVA_HOME%/bin 目录,直接执行 jconsole
CMD 切换到 JAVA_HOME bin 目录,直接执行 jconsole

即可打开 Java 监视和管理控制台:
即可打开 Java 监视和管理控制台

本地进程列表里显示了所有本地执行中的 Java 进程,双击你感兴趣的那个进程(比如 PID 为 8504 那个),即可对该进程进行监控了:
双击你感兴趣的那个进程
 

 

4. 远程监控 Java 进程

要对 Java 进程进行远程监控,在启动它的时候需要启用 JMX。
以远程主机上的 tomcat 为例,先为 jmx 找一个可用的远程端口,比如 9999:
先为 jmx 找一个可用的远程端口

No news is good news~在 %TOMCAT_HOME%/bin/catalina.sh 文件的前面加上以下配置:

 

 

  1. JAVA_OPTS=”-Xms1024m -Xmx2048m -XX:MaxPermSize=128m -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false”  
JAVA_OPTS="-Xms1024m -Xmx2048m -XX:MaxPermSize=128m -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
  • 1



如图
tomcat的JAVA_OPTS配置
 

这样写在 tomcat 关闭的时候(执行 %tomcat%/bin/shutdown.sh)会报端口已使用异常:

错误: 代理抛出异常错误: java.rmi.server.ExportException: Port already in use: 9999; nested exception is: 
        java.NET.BindException: 地址已在使用

这是因为 tomcat 在启动、停止的时候都会执行 JAVA_OPTS 配置。这样就只能使用 kill -9 来关闭 tomcat 了…

解决办法是把监控配置写在 CATALINA_OPTS 里:

JAVA_OPTS=”-Xms1024m -Xmx2048m -XX:MaxPermSize=128m”
CATALINA_OPTS=”-Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false”

就可以了。CATALINA_OPTS 配置只是在 tomcat 启动的时候执行。参考资料:https://bowerstudios.com/node/636。

然后重启 tomcat,在本机打开 Java 监视和管理控制台,”远程进程” 输入远程主机名和 jmx 端口号:

jmx 远程连接

点击 “连接” 按钮,即可对远程主机上的 tomcat 进行实时监控了:
即可对远程主机上的 tomcat 进行实时监控了
 

 

5. jconsole 提供的一些有用信息

接着第 4 步的案例往下看。

5.1. JVM 设定信息是否起作用检查

点击 “VM 概要” 可以查看到刚才我们设定的 JAVA_OPTS 的一些参数已经奏效了:
JAVA_OPTS 的一些参数已经奏效
 

5.2. tomcat 线程池、自定义线程池数量情况实时监控

还在为 tomcat 线程池的神秘面纱而头疼?还在为自己定义的线程池 “黑盒” 一般而苦恼?看看下图:
tomcat线程情况

我们的 tomcat 刚启动,从上图可以看出只有一个 http-8080-Acceptor-0 线程,我们去访问一下我们的项目,然后再回来看看:
http-8080 线程一下子增长到了 8 个

http-8080 线程一下子增长到了 8 个。是不是一切一目了然,尽在掌握之中?

5.3. 内存使用实际消耗

点击 Java 监视和管理控制台 “内存” 叶项,可以看到 tomcat 堆内存的使用情况:
tomcat 堆内存的使用情况

图表里有很多选项:
图表里有很多选项

我们看一下 Eden 区:
Eden区

Eden 区基本和整个堆内存的走势差不多。再看 Survivor 区:
Survivor区

Survivor 区在较短时间内的走势相对平稳。再看 Old Gen 区:
再看 Old Gen 区

这个走势更加平稳,而且对比 Survivor 区、Old Gen 区两张图,可以很明显地看出,在大约 19:58 那个时刻有将一批对象从 Survivor 区移到 Old Gen 区。最后看 Perm Gen 区。
这个走势最平稳了。可以明显看出,在大约 19:58,在我们访问一下我们的项目的时候,一些新的 class 等静态资源加载到了 JVM 中。5.4 的加载类数的图也证实了这一点。

5.4. tomcat 加载类的情况

tomcat 加载类的情况
 

 

6. 配合 jmap 的使用

先找到我们 tomcat 进程的 PID 是 13863

先找到我们 tomcat 进程的 PID 是 13863,然后执行 jmap -heap 13863:
jmap -heap 13863
 

Heap Configuration 里列的基本就是我们刚才配的那些,比如 MaxHeapSize 是 2048 MB,MaxPermSize 是 128 MB。这个和 5.1 里的是一样的。

 

参考资料

  • Sun Java System Application Server Enterprise Edition 8.2 Performance Tuning Guide
  • Frequently Asked Questions About the Java HotSpot VM

你可能感兴趣的:(08-MID-01-环境搭建)