4.3.3 VisualVM:多合-故障处理工具

4.3.3 VisualVM:多合-故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一, 曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。Oracle曾在VisualVM的软件说明中写上了“All-in-One”的字样,预示着它除了常规的运行监视、故障处理外,还将提供其他方面的能力,譬如性能分析(Profiling)。VisualVM的性能分析功能比起JProfiler、YourKit等专业且收费的Profiling工具都不遑多让。而且相比这些第三方工具,VisualVM还有一个很大的优点:不需要被监视的程序基于特殊Agent去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。这个优点是JProfiler、YourKit等工具无法与之媲美的。

1.VisualVM兼容范围与插件安装

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

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

VisualVM在JDK 6 Update 7中首次发布,但并不意味着它只能监控运行于JDK 6上的程序,它具备很优秀的向下兼容性,甚至能向下兼容至2003年发布的JDK 1.4.2版本[^1],这对无数处于已经完成实施、正在维护的遗留项目很有意义。当然,也并非所有功能都能完美地向下兼容,主要功能的兼容性见表4-16所示。

表4-16 VisualVM主要功能兼容性列表

image-20210918201945086

image-20210918202039273

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

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

VisualVM的插件可以手工进行安装,在网站[^2]上下载nbm包后,点击“工具->插件->已下载”菜单,然后在弹出对话框中指定nbm包路径便可完成安装。独立安装的插件存储在VisualVM的根目录, 譬如JDK 9之前自带的VisulalVM,插件安装后是放在JDK_HOME/lib/visualvm中的。手工安装插件并不常用,VisualVM的自动安装功能已可找到大多数所需的插件,在有网络连接的环境下,点击“工具-> 插件菜单”,弹出如图4-17所示的插件页签,在页签的“可用插件”及“已安装”中列举了当前版本VisualVM可以使用的全部插件,选中插件后在右边窗口会显示这个插件的基本信息,如开发者、版本、功能描述等。

image-20210918202135810

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

image-20210918202212740

图4-18 VisualVM插件安装过程

选择一个需要监视的程序就可以进入程序的主界面了,如图4-19所示。由于VisualVM的版本以及选择安装插件数量的不同,读者看到的页签可能和笔者的截图有所差别。

image-20210918202246133

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

2.生成、浏览堆转储快照

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

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

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

image-20210918202441372

图4-20 浏览dump文件

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

3.分析程序性能

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

要开始性能分析,先选择“CPU”和“内存”按钮中的一个,然后切换到应用程序中对程序进行操作,VisualVM会记录这段时间中应用程序执行过的所有方法。如果是进行处理器执行时间分析,将会统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。等要分析的操作执行结束后,点击“停止”按钮结束监控过程,如图4-21所示。

image-20210918202550203

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

图4-21中是对Eclipse IDE一段操作的录制和分析结果,读者分析自己的应用程序时,可根据实际业务复杂程度与方法的时间、调用次数做比较,找到最优化价值方法。

4.BTrace动态日志跟踪

BTrace[^3]是一个很神奇的VisualVM插件,它本身也是一个可运行的独立程序。BTrace的作用是在不中断目标程序运行的前提下,通过HotSpot虚拟机的Instrument功能[^4]动态加入原本并不存在的调试代码。这项功能对实际生产中的程序很有意义:如当程序出现问题时,排查错误的一些必要信息时 (譬如方法参数、返回值等),在开发时并没有打印到日志之中以至于不得不停掉服务时,都可以通过调试增量来加入日志代码以解决问题。

在VisualVM中安装了BTrace插件后,在应用程序面板中右击要调试的程序,会出现“TraceApplication…”菜单,点击将进入BTrace面板。这个面板看起来就像一个简单的Java程序开发环境,里面甚至已经有了一小段Java代码,如图4-22所示。

image-20210918202730151

图4-22 BTrace动态跟踪

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

代码清单4-10 BTrace跟踪演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BTraceTest {

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

public static void main(String[] args) throws IOException {
BTraceTest test = new BTraceTest();
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));
}
}
}

假设这段程序已经上线运行,而我们现在又有了新的需求,想要知道程序中生成的两个随机数是什么,但程序并没有在执行过程中输出这一点。此时,在VisualVM中打开该程序的监视,在BTrace页签填充TracingScript的内容,输入调试代码,如代码清单4-11所示,即可在不中断程序运行的情况下做到这一点。

代码清单4-11 BTrace调试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TracingScript {
@OnMethod(
clazz="org.fenixsoft.monitoring.BTraceTest",
method="add",
location=@Location(Kind.RETURN)
)

public static void func(@Self org.fenixsoft.monitoring.BTraceTest 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按钮后稍等片刻,编译完成后,Output面板中会出现“BTrace code successfuly deployed”的字样。当程序运行时将会在Output面板输出如图4-23所示的调试信息。

image-20210918204808350

图4-23 BTrace跟踪结果

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

BTrace能够实现动态修改程序行为,是因为它是基于Java虚拟机的Instrument开发的。Instrument是Java虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)的重要组件,提供了一套代理 (Agent)机制,使得第三方工具程序可以以代理的方式访问和修改Java虚拟机内部的数据。阿里巴巴开源的诊断工具Arthas也通过Instrument实现了与BTrace类似的功能。

[^1]: 早于JDK 6的平台,需要打开-Dcom.sun.management.jmxremote参数才能被VisualVM管理。
[^2]: 插件中心地址:https://visualvm.github.io/pluginscenters.html。
[^3]: 官方主页:https://github.com/btraceio/btrace。
[^4]: 是JVMTI中的主要组成部分,HotSpot虚拟机允许在不停止运行的情况下,更新已经加载的类的代码。