Java四种 可视化故障处理工具 详解

编程知识 更新时间:2023-05-02 22:47:38

目录

    • 一、简言
    • 二、JHSDB:基于服务性代理的调试工具
      • 2.1、要验证的代码
      • 2.2、启动JHSDB
      • 2.3、在堆当中寻找对象
      • 2.4、查看对象详情
      • 2.5、找出 堆中 引用它们的指针
      • 2.6、找出 栈中 引用它们的指针
    • 三、JConsole:Java监视与管理控制台
      • 3.1、启动JConsole
      • 3.2、内存监控
      • 3.3、线程监控
      • 3.4、监控死锁
    • 四、VisualVM:多合-故障处理工具
      • 4.1、启动VisualVM
      • 4.2、VisualVM插件安装
      • 4.3、生成、浏览堆转储快照
      • 4.4、分析程序性能
      • 4.5、BTrace动态日志跟踪
    • 五、Java Mission Control:可持续在线的监控工具
      • 5.1、远程连接
      • 5.2、飞行记录器

一、简言

JDK中除了附带大量的命令行工具外,还提供了几个功能集成度更高的可视化工具(上一章重点讲解了命令行工具),用户可以使 用这些可视化工具以更加便捷的方式进行进程故障诊断和调试工作。

这类工具主要包括JConsole、 JHSDB、VisualVM和JMC四个。

本篇者准备了一些代码样例,稍后将会使用几款工具去监控、分析这些代码存在的问题,算是本节简单的 实战演练。读者可以把在可视化工具观察到的数据、现象,与前面两章中讲解的理论知识进行互相验 证。

二、JHSDB:基于服务性代理的调试工具

JHSDB是一款基于服务性代理实现的进程外调试工具。服务性代理是 HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言(含少量JNI代码)实现的 API集合。

2.1、要验证的代码

本次,我们要借助JHSDB来分析一下下面代码,并通过实验来回答一个简单问 题:staticObj、instanceObj、localObj这三个变量本身(而不是它们所指向的对象)存放在哪里?

答案了解过JVM模型的应该都知道:首先所指的对象,毫无疑问肯定是堆当中,staticObj随着Test的类型信息存放在方法区instanceObj随着Test的对象实 例存放在Java堆localObject则是存放在foo()方法栈帧的局部变量表中。现在要做的是通过JHSDB来实践验证这一点。

启动参数:

-Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops

-XX:-UseCompressedOops:开启普通对象指针压缩,会在内存中消耗20个字节,o指针占4个字节,Object对象占16个字节。不开启的话,会在内存中消耗24个字节,o 指针占8个字节,Object对象占16个字节。

public class JHSDB_TestCase {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done"); // 这里设一个断点 
        }
    }

    private static class ObjectHolder {
    }

    public static void main(String[] args) {
        Test test = new JHSDB_TestCase.Test();
        test.foo();
    }
}

首先,我们要确保这三个变量已经在内存中分配好,然后将程序暂停下来,以便有空隙进行实 验,这只要把断点设置在固定的地方,然后在调试模式下运行程序即可。

2.2、启动JHSDB

启动方式:https://wwwblogs/mouren/p/14339663.html

然后jps -l 查询要监控应用的进程id

通过以下步骤我们就可以开启监控该进程下的项目情况:

2.3、在堆当中寻找对象

既然我们要查找引用这三个对象的指针存放在哪里,不妨从这三个对象开 始着手,先把它们从Java堆中找出来。

运行参数中指定了使 用的是Serial收集器,图中我们看到了典型的Serial的分代内存布局,Heap Parameters窗口中清楚列出了 新生代的Eden、S1、S2和老年代的容量(单位为字节)以及它们的虚拟内存地址起止范围。


打开Windows->Console窗 口,使用scanoops命令在Java堆的新生代(从Eden起始地址到To Survivor结束地址)范围内查找 ObjectHolder的实例,结果如下所示:

在内存当中,内存首先都是连着的,而下面这个命令的意思就是查找堆当中0x0000000012400000 到 0x0000000012750000 内存当中的对象位置。

scanoops 0x0000000012400000 0x0000000012750000 com/gzl/cn/JHSDB_TestCase$ObjectHolder
  • com/gzl/cn/是包名
  • JHSDB_TestCase是类名
  • $只是一个类名和对象名的连接符
  • ObjectHolder对象的类名


果然找出了三个实例的地址,而且它们的地址都落到了Eden的范围之内,算是顺带验证了一般情 况下新对象在Eden中创建的分配规则。再使用Tools->Inspector功能确认一下这三个地址中存放的对 象,结果如图所示。

2.4、查看对象详情

Inspector就是可以查看指定内存的对象的详情。也可以查看变量指向的地址。也可以查看对象属性当中指向的别的对象的地址。但是有一点不能查出,就是哪个指针指向了他,在这块是查不出来的。

Inspector为我们展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关 系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口 方法表(itable)等。由于我们的确没有在ObjectHolder上定义过任何字段,所以图中并没有看到任何 实例字段数据,读者在做实验时不妨定义一些不同数据类型的字段,观察它们在HotSpot虚拟机里面是 如何存储的。

2.5、找出 堆中 引用它们的指针

接下来要根据堆中对象实例地址找出引用它们的指针,还是在window界面的console界面当中使用命令查询:

revptrs命令的功能就是根据内存地址 查找 堆当中 哪个变量地址 指向了这个对象地址。

果然找到了一个引用该对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地 址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面 有一个名为staticObj的实例字段,如图所示。并且staticObj指向了刚刚查到的对象。

JDK 7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对 象存放在一起,存储于Java堆之中,从我们的实验中也明确验证了这一点。接下来继续查找第二个 对象实例:

这个结果完全符合我们的预期,第二个ObjectHolder的指针是在Java堆中JHSDB_TestCase$Test对 象的instanceObj字段上。但是我们采用相同方法查找第三个ObjectHolder实例时,JHSDB返回了一个 null,表示未查找到任何结果:

2.6、找出 栈中 引用它们的指针

看来revptrs命令并不支持查找栈上的指针引用,不过没有关系,得益于我们测试代码足够简洁, 人工也可以来完成这件事情。在Java Thread窗口选中main线程后点击Stack Memory按钮查看该线程的 栈内存。


这个线程只有两个方法栈帧,尽管没有查找功能,但通过肉眼观察在地址0x0000000002baf4d8上 的值正好就是0x000000001266d040,而且JHSDB在旁边已经自动生成注释,说明这里确实是引用了一ObjectHolder对象。至此,本次实验中三个对象均已找到,并成功追 溯到引用它们的地方,也就实践验证了开篇中提出的这些对象的引用是存储在什么地方的问题。

注意:在JDK 7以前,即还没有开始“去永久代”行动时,这些静态变量是存放在永久代上的,JDK 7起把 静态变量、字符常量这些从永久代移除出去。

三、JConsole:Java监视与管理控制台

JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进 行信息收集和参数动态调整。JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以 运行于虚拟机之上的软件中,典型的如中间件大多也基于JMX来实现管理与监控。虚拟机对JMX MBean的访问也是完全开放的,可以使用代码调用API、支持JMX协议的管理控制台,或者其他符合 JMX规范的软件进行访问

3.1、启动JConsole

通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询。双击选择其中一个进程便可进入主界面开始监控。 JMX支持跨服务器的管理,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行 监控

双击它进入JConsole主界面,可以看到主 界面里共包括“概述”“内存”“线程”“类”“VM摘要”“MBean”六个页签,如图所示。

“概述”页签里显示的是整个虚拟机主要运行数据的概览信息,包括“堆内存使用情况”“线 程”“类”“CPU使用情况”四项信息的曲线图。

3.2、内存监控

“内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存(被收集器直 接管理的Java堆和被间接管理的方法区)的变化趋势。我们通过运行代码来体验一下 它的监视功能。运行时设置的虚拟机参数为:

-Xms100m -Xmx100m -XX:+UseSerialGC

这里MonitoringTest是笔者准备的“反面教材”代码之一,代码如下:

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

public class MonitoringTest {
    /*** 内存占位符对象,一个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<OOMObject>();
        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);
    }
}

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

注意:由于打开JConsole 还需要一定的时间,可以进行打断点拦截,然后等打开连接上之后再放开断点。

程序运行后,在“内存”页签中可以看到内存池Eden区的运行趋势呈现折线状,如下图所示。监 视范围扩大至整个堆后,会发现曲线是一直平滑向上增长的。从柱状图可以看到,在1000次循环执行 结束,运行了System.gc()后,虽然整个新生代Eden被清空了,但是代表老年代的柱 状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。笔者的分析 就到此为止,提两个小问题供读者思考一下,答案稍后公布。

  1. 虚拟机启动参数只限制了Java堆为100MB,但没有明确使用-Xmn参数指定新生代大小,读者 能否从监控图中估算出新生代的容量?
  2. 为何执行了System.gc()之后,下中代表老年代的柱状图仍然显示峰值状态,代码需要如何 调整才能让System.gc()回收掉填充到堆中的对象?

从这里还可以发现一点,survivor当中一直是满的状态,原因就是对象一直存活,导致只能担保向老年代存放。

问题1答案:上图显示Eden空间为27328KB,因为没有设置-XX:SurvivorRadio参数,所以Eden 与Survivor空间比例的默认值为8∶1,因此整个新生代空间大约为27328KB×125%=34160KB。

问题2答案:执行System.gc()之后,空间未能回收是因为Listlist对象仍然存活, fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把 System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。

通过以下试验,确实可以清空内存,但是要注意记着要GC过后,进行等待一段时间,不然他可能垃圾没清理完,垃圾收集是单独的线程,主线程已经终止了,会导致你看到老年代根本没释放出去。

3.3、线程监控

如果说JConsole的“内存”页签相当于可视化的jstat命令的话,那“线程”页签的功能就相当于可视化 的jstack命令了,遇到线程停顿的时候可以使用这个页签的功能进行分析。线程长时间停顿的主要原因有等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等 待等,以下代码将分别演示这几种情况。

代码示例:

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ThreadTest {
    /*** 线程死循环演示 */
    public static void createBusyThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true)
                {
                    ;
                }
            }
        }, "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();
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
        createBusyThread();
        br.readLine();
        Object obj = new Object();
        createLockThread(obj);
    }
}

Object的方法:Wait()方法和notify()方法:当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁。当它被一个notify()方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到wait()前的中断现场。

程序运行后,首先在“线程”页签中选择main线程,如下图所示。堆栈追踪显示BufferedReader的 readBytes()方法正在等待System.in的键盘输入,这时候线程为Runnable状态,Runnable状态的线程仍会 被分配运行时间,但readBytes()方法检查到流没有更新就会立刻归还执行令牌给操作系统,然后等待输入到控制台,并且点击回车键告诉流写完了,而readBytes就是读的控制台的,这种等待 只消耗很小的处理器资源。

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

再次控制台输入字符,然后点击enter开启testLockThread线程,显示testLockThread线程在等待lock对象的notify()或notifyAll()方法的出现,线程这时候处于 WAITING状态,在重新唤醒前不会被分配执行时间。

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

3.4、监控死锁

下面演示了一个无法再被激活的死锁等待。

代码示例:

public class ThreadTest1 {
    /*** 线程死锁等待演示 */
    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();
        }
    }
}

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

运行结果:
运行之后会发现程序卡着不动了,没错就是死锁了。打开JConsole,点击检测死锁。

线程Thread-87在等待一个被线程Thread-82持有的Integer对象,而点击线 程Thread-82则显示它也在等待一个被线程Thread-87持有的Integer对象,这样两个线程就互相卡住,除 非牺牲其中一个,否则死锁无法释放。

四、VisualVM:多合-故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一, 曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。

VisualVM中“概述”“监视”“线程”“MBeans”的功能与前面介绍的JConsole差别不大,读者可根据上 一节内容类比使用,这里笔者挑选几个有特色的功能和插件进行简要介绍。

https://jingyan.baidu/article/6525d4b102c3c0ec7c2e9416.html

4.1、启动VisualVM

在jdk的bin目录下,不用安装就可以使用

打开之后的样子

通过命令也可以直接打开jvisualvm

4.2、VisualVM插件安装

VisualVM基于NetBeans平台开发工具,所以一开始它就具备了通过插件扩展功能的能力,有了插 件扩展支持,VisualVM可以做到:

  1. 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
  2. 监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)。
  3. dump以及分析堆转储快照(jmap、jhat)。
  4. 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。
  5. 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快 照发送开发者处进行Bug反馈。
  6. 其他插件带来的无限可能性。

初始状态下的VisualVM并没有加载 任何插件,虽然基本的监视、线程面板的功能主程序都以默认插件的形式提供,但是如果不在 VisualVM上装任何扩展插件,就相当于放弃它最精华的功能,和没有安装任何应用软件的操作系统差 不多。

怎么安装插件?

点击工具-》然后有插件选项,这里我在检测可用插件的时候直接报错了。

这是因为插件中心的地址不可用,处理方式如下:

1、打开网址:https://visualvm.github.io/pluginscenters.html
2、在右侧选择JDK版本


3、我用的jdk1.8.0_101版本,然后复制的上面那段保存后就可以了。

开始安装插件

可根据自己的工作需要和兴趣选择合适的插件,然后点击“安装”按钮,弹出如下图所示的 下载进度窗口,跟着提示操作即可完成安装。

VisualVM中“概述”“监视”“线程”“MBeans”的功能与前面介绍的JConsole差别不大,读者可根据上 一节内容类比使用,这里笔者挑选几个有特色的功能和插件进行简要介绍。

4.3、生成、浏览堆转储快照

在VisualVM中生成堆转储快照文件有两种方式,可以执行下列任一操作:

  1. 在“应用程序”窗口中右键单击应用程序节点,然后选择“堆Dump”。
  2. 在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中单击“堆 Dump”。

生成堆转储快照文件之后,应用程序页签会在该堆的应用程序下增加一个以[heap-dump]开头的子 节点,并且在主页签中打开该转储快照,如下图所示。如果需要把堆转储快照保存或发送出去,就 应在heapdump节点上右键选择“另存为”菜单,否则当VisualVM关闭时,生成的堆转储快照文件会被当 作临时文件自动清理掉。打开一个已经存在的堆转储快照文件通过文件菜单中的“装入”功能, 选择硬盘上的文件即可。

  1. 概要面板可以看到应用程序dump时的运行时参数、System.getPro-perties()的内容、 线程堆栈等信息
  2. 面板则是以类为统计口径统计类的实例数量、容量信息;
  3. 实例面板不能直接 使用,因为VisualVM在此时还无法确定用户想查看哪个类的实例,所以需要通过“类”面板进入, 在“类”中选择一个需要查看的类,然后双击即可在“实例”里面看到此类的其中500个实例的具体属性信 息;
  4. OQL控制台面板则是运行OQL查询语句的,同jhat中介绍的OQL功能一样。

4.4、分析程序性能

在Profiler页签中,VisualVM提供了程序运行期间方法级的处理器执行时间分析以及内存分析。做 Profiling分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境使用这项功能,或者改用 JMC来完成,JMC的Profiling能力更强,对应用的影响非常轻微。

要开始性能分析,先选择“CPU”和“内存”按钮中的一个,然后切换到应用程序中对程序进行操 作,VisualVM会记录这段时间中应用程序执行过的所有方法。

  1. 如果是进行处理器执行时间分析,将会 统计每个方法的执行次数、执行耗时;
  2. 如果是内存分析,则会统计每个方法关联的对象数以及这些对 象所占的空间。等要分析的操作执行结束后,点击“停止”按钮结束监控过程,如下所示。

注意 在JDK 5之后,在客户端模式下的虚拟机加入并且自动开启了类共享——这是一个在多 虚拟机进程共享rt.jar中类数据以提高加载速度和节省内存的优化,而根据相关Bug报告的反映, VisualVM的Profiler功能会因为类共享而导致被监视的应用程序崩溃,所以读者进行Profiling前,最好在 被监视程序中使用-Xshare:off参数来关闭类共享优化

发现问题:点击开始分析的时候不管-Xshare:off关闭没关闭,都连不上,直接一直如下图一样在转圈。

使用如下命令进行启动VisualVM即可解决:

jvisualvm -J-Dorg.netbeans.profiler.separateConsole=true  

-J即表示JVM OPTION:允许带JVM参数启动

连上之后程序当中会出现如下日志:

这个是分析的内存

这是分析的cpu:

注意:当点击开始分析的时候,我们需要运行方法。当进入一个方法时,线程会发出一个“method entry”的事件,当退出方法时同样会发出一个“method exit”的事件,这些事件都包含了时间戳。然后 VisualVM 会把每个被调用方法的总的执行时间和调用的次数按照运行时长展示出来。

4.5、BTrace动态日志跟踪

当程序出现问题时,排查错误的一些必要信息时 (譬如方法参数、返回值等),在开发时并没有打印到日志之中以至于不得不停掉服务时,都可以通过调试增量来加入日志代码以解决问题

笔者准备了一段简单的Java代码来演示BTrace的功能:产生两个1000以内的随机整数,输出这两 个数字相加的结果。

代码示例: 这段代码是要放到ider里面执行的。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class TraceTest {
    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) throws IOException {
        TraceTest test = new TraceTest();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < 10; i++) {
            reader.readLine();
            int a = (int) Math.round(Math.random() * 1000);
            int b = (int) Math.round(Math.random() * 1000);
            System.out.println(test.add(a, b));
        }
    }
}

假设上面这段程序已经上线运行,而我们现在又有了新的需求,想要知道程序中生成的两个随机数是 什么,但程序并没有在执行过程中输出这一点。这时候我们就可以通过Trace来跟踪。

选中一个我们要进行监控的jvm实例,右键点击,然后选择 Trace application:

代码示例: 这里的com.gzl.TraceTest是刚刚那段代码的全类名。注意下面这段代码是要放到VisualVM当中的,而不是ide当中的。

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
    @OnMethod(clazz = "com.gzl.TraceTest", method = "add", location = @Location(Kind.RETURN))
    public static void func(@Self com.gzl.cn.TraceTest instance, int a, int b, @Return int result) {
        println("调用堆栈:");
        jstack();
        println(strcat("方法参数A:", str(a)));
        println(strcat("方法参数B:", str(b)));
        println(strcat("方法结果:", str(result)));
    }
}

点击Start按钮后稍等片刻,便可以监控生成的随机数参数。


BTrace的用途很广泛,打印调用堆栈、参数、返回值只是它最基础的使用形式,在它的网站上有 使用BTrace进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等的使用案例,有兴趣的 读者可以去网上了解相关信息。

五、Java Mission Control:可持续在线的监控工具

Java Mission Control简称JMC,JMC不仅可以下载到独立程序,更常 见的是作为Eclipse的插件来使用。JMC与虚拟机之间同样采取JMX协议进行通信。

  1. JMC一方面作为 JMX控制台,显示来自虚拟机MBean提供的数据;
  2. 另一方面作为JFR的分析工具,展示来自JFR的数 据。启动后JMC的主界面如下图所示。

什么是JFR?

JFR就是下面所显示的飞行记录器数据,JFR是一套内建在HotSpot虚拟机里面的监控和基于事件的信息搜集框架,与其他的监控工具(如 JProfiling)相比,Oracle特别强调它“可持续在线”(Always-On)的特性。JFR在生产环境中对吞吐量 的影响一般不会高于1%(甚至号称是Zero Performance Overhead),而且JFR监控过程的开始、停止都 是完全可动态的,即不需要重启应用。JFR的监控对应用也是完全透明的,即不需要对应用程序的源 码做任何修改,或者基于特定的代理来运行。

5.1、远程连接

如果需要监控其他服务器上的虚拟机,可在“文件->连接”菜单中创建远 程连接。

这里要填写的信息应该在被监控虚拟机进程启动的时候以虚拟机参数的形式指定,以下是一份被 监控端的启动参数样例:

-Dcom.sun.management.jmxremote.port=9999 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=192.168.31.4 
-XX:+UnlockCommercialFeatures 
-XX:+FlightRecorder

5.2、飞行记录器

飞行记录报告里包含以下几类信息:

  1. 一般信息:关于虚拟机、操作系统和记录的一般信息。
  2. 内存:关于内存管理和垃圾收集的信息。
  3. 代码:关于方法、异常错误、编译和类加载的信息。
  4. 线程:关于应用程序中线程和锁的信息。
  5. I/O:关于文件和套接字输入、输出的信息。
  6. 系统:关于正在运行Java虚拟机的系统、进程和环境变量的信息。
  7. 事件:关于记录中的事件类型的信息,可以根据线程或堆栈跟踪,按照日志或图形的格式查看

JFR的基本工作逻辑是开启一系列事件的录制动作,当某个事件发生时,这个事件的所有上下文 数据将会以循环日志的形式被保存至内存或者指定的某个文件当中,循环日志相当于数据流被保留在 一个环形缓存中,所以只有最近发生的事件的数据才是可用的。JMC从虚拟机内存或者文件中读取并 展示这些事件数据,并通过这些数据进行性能分析。

通俗点理解可以把它当成飞机上的黑匣子。

JFR提供的数据质量通常也要比其他工具通过代 理形式采样获得或者从MBean中取得的数据高得多。以垃圾搜集为例,HotSpot的MBean中一般有各个 分代大小、收集次数、时间、占用率等数据(根据收集器不同有所差别),这些都属于“结果”类的信 息,而JFR中还可以看到内存中这段时间分配了哪些对象、哪些在TLAB中(或外部)分配、分配速率 和压力大小如何、分配归属的线程、收集时对象分代晋升的情况等,这些就是属于“过程”类的信息, 对排查问题的价值是难以估量的。

更多推荐

Java四种 可视化故障处理工具 详解

本文发布于:2023-04-29 04:23:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/5b480136a14aee147d8a5c31766fa826.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:四种   详解   故障处理   工具   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!

  • 110147文章数
  • 28005阅读数
  • 0评论数