VisualVM 是功能最强大的运行监视和故障处理程序之一,在 JDK 6 中首次发布,曾经是 Oracle 官方主力发展的虚拟机故障处理工具,不过现在它已经成为一个独立发展的开源项目。

VisualVM 除了常规的运行监视、故障处理外,还提供诸如性能分析等其他方面的能力。而且 VisualVM 的性能分析不需要被监视的程序基于特殊 Agent 去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。

VisualVM 插件安装

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

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

初始状态下的 VisualVM 并没有加载任何插件,虽然基本的监视、线程面板的功能主程序都以默认插件的形式提供了,但是如果不在 VisualVM 上装任何扩展插件,就相当于放弃它最精华的功能,和没有安装任何应用软件的操作系统差不多。
image.png
VisualVM 的自动安装功能可以找到大多数所需的插件,在有网络连接的环境下,点击 工具->插件菜单 会弹出如下图所示的插件页签,在页签的可用插件及已安装中列举了当前版本 VisualVM 可以使用的全部插件,选中插件后在右边窗口会显示这个插件的基本信息,如开发者、版本、功能描述等。
image.png

连接应用程序

Visual VM 支持本地连接,也支持远程 JMX 连接。Java 应用程序可以通过以下参数打开 JMX 端口:

  1. -Djava.rmi.server.hostname=127.0.0.1
  2. -Dcom.sun.management.jmxremote
  3. -Dcom.sun.management.jmxremote.port=8888
  4. -Dcom.sun.management.jmxremote.authenticate=false
  5. -Dcom.sun.management.jmxremote.ssl=false

然后我们就可以通过下图所示的操作来添加远程 JMX 连接:
image.png
在弹出的对话框中填写远程远程计算机地址、端口。如果需要验证,则再填写用户名和密码:
image.png
添加成功后,即可进入远程 JMX 连接:
image.png

功能介绍

image.png
VisualVM 中的概述、监视、线程、MBeans 页签的功能与 JConsole 的差别基本不大,下面挑选几个有特色的功能和插件进行简要介绍。

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

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

  • 在 “应用程序” 窗口中右键单击应用程序节点,然后选择:堆Dump

image.png

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

image.png
生成堆转储快照文件之后,应用程序页签会在该堆的应用程序下增加一个以 [heapdump] 开头的子节点,并且在主页签中打开该转储快照。如果需要保存堆转储快照,可以在 heapdump 节点上右键选择 “另存为” 菜单,否则当 VisualVM 关闭时,生成的堆转储快照文件会被当作临时文件自动清理掉。要打开一个已经存在的堆转储快照文件,通过文件菜单中的 “装入” 功能,选择文件即可。
image.png
堆页签中的 “概要” 面板可以看到应用程序 dump 时的运行时参数、System.getProperties() 的内容、线程堆栈等信息;”类” 面板则是以类为统计口径统计类的实例数量、容量信息;”实例” 面板不能直接使用,因为 VisualVM 在此时还无法确定用户想查看哪个类的实例,所以需要在 “类” 面板中选择一个需要查看的类,然后双击即可在 “实例” 里面看到此类的其中 500 个实例的具体属性信息。

2. 线程快照

image.png
通过执行线程 Dump 可以获取当前线程的瞬时线程栈信息:
image.png

3. 分析程序性能

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

要开始性能分析,先选择 “CPU” 和 “内存” 按钮中的一个,然后切换到应用程序中对程序进行操作,VisualVM 会记录这段时间中应用程序执行过的所有方法。如果是进行处理器执行时间分析,将会统计每个方法的执行次数以及执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。
image.png
在 Visual VM 的默认统计信息中,并不包含 JDK 的内置对象的函数调用统计,比如 java.* 包中的类。如果需要统计 JDK 内的方法调用情况,需要单击右上角的设置选项,手工进行配置:
image.png

4. BTrace 动态日志跟踪

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

在 VisualVM 中安装了 BTrace 插件后,在 “应用程序” 面板中右击要调试的程序,会出现 Trace application 菜单,点击将进入 BTrace 面板。这个面板看起来就像一个简单的 Java 程序开发环境,如下图所示。
image.png
下面通过一段简单的 Java 代码来演示 BTrace 的功能:产生两个 1000 以内的随机整数,输出这两个数字相加的结果:

  1. public class AdderDemo {
  2. public static void main(String[] args) {
  3. Runnable adder = () -> {
  4. int randomA = new Random().nextInt(1000);
  5. int randomB = new Random().nextInt(1000);
  6. System.out.println("result is:" + add(randomA, randomB));
  7. };
  8. ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
  9. // 每3秒进行一次累加
  10. scheduler.scheduleAtFixedRate(adder, 3L, 3L, TimeUnit.SECONDS);
  11. }
  12. private static int add(int a, int b) {
  13. return a + b;
  14. }
  15. }

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

  1. /* BTrace Script Template */
  2. import com.sun.btrace.annotations.*;
  3. import static com.sun.btrace.BTraceUtils.*;
  4. @BTrace
  5. public class TracingScript {
  6. @OnMethod(
  7. clazz="org.xl.java.concurrence.AdderDemo",
  8. method="add",
  9. location=@Location(Kind.RETURN)
  10. )
  11. public static void func(@Self org.xl.java.concurrence.AdderDemo instance,int a, int b,@Return int result) {
  12. println("调用堆栈:");
  13. jstack();
  14. println(strcat("方法参数A:",str(a)));
  15. println(strcat("方法参数B:",str(b)));
  16. println(strcat("方法结果:",str(result)));
  17. }
  18. }

点击 Start 按钮后稍等片刻,编译完成后,Output 面板中会出现 BTrace code successfuly deployed 的字样。当程序运行时将会在 Output 面板输出如下图所示的调试信息。
image.png
BTrace 的用途很广泛,打印调用堆栈、参数、返回值只是最基础的使用形式,在它的网站上有使用 BTrace 进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等的使用案例。

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

参考链接:
https://coderbee.net/index.php/jvm/20170514/1519
https://blogs.sap.com/2019/11/15/brief-introduction-to-btrace-a-very-useful-dynamic-tracing-tool/