一、VisualVM 简介
1.1 什么是 VisualVM
VisualVM 是一款基于 Java 平台的可视化工具,主要用于监控、分析和调试运行中的 Java 应用程序。它整合了多个 JDK 提供的命令行工具(如 jstat、jstack、jmap、jinfo),提供统一的图形界面,使开发者能够实时查看内存使用、线程状态、CPU 消耗等关键性能指标。
1.2 历史与发展
VisualVM 最初由 Sun Microsystems 开发,随着 Oracle 收购 Sun 后成为 JDK 的附带工具之一。在 Java 9 后,它从 JDK 分离出来成为独立项目,并持续更新。
1.3 适用人群与应用场景
VisualVM 适用于所有级别的 Java 开发者,尤其适用于如下场景:
-
性能调优
-
内存泄漏排查
-
线程死锁分析
-
实时系统资源监控
二、安装与配置
2.1 下载地址
VisualVM 官方网站:https://visualvm.github.io/
2.2 安装步骤
-
访问官网下载页面,根据操作系统选择合适的版本。
-
解压缩安装包至本地目录。
-
启动方式:执行
bin/visualvm
(Linux/macOS)或visualvm.exe
(Windows)。
2.3 系统要求
-
Java 8 及以上版本。
-
JDK 安装路径已配置至系统环境变量。
2.4 与 JDK 的集成
在 VisualVM 中集成 JDK 的工具可通过如下方式实现:
export JAVA_HOME=/path/to/jdk
export PATH=$JAVA_HOME/bin:$PATH
2.5 配置远程监控权限(示例 JMX 设置)
在目标 Java 应用的启动参数中添加:
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Djava.rmi.server.hostname=127.0.0.1
三、用户界面概览
3.1 主窗口介绍
VisualVM 的主界面由以下几部分组成:
-
Applications 面板:列出当前可监控的本地及远程应用。
-
概览(Overview):展示应用基本信息、JVM 参数等。
-
监视(Monitor):实时展示堆内存、线程、GC 活动图表。
-
线程(Threads):线程活动、栈帧、状态信息。
-
采样器(Sampler)/分析器(Profiler):进行 CPU、内存分析。
3.2 基本导航
通过双击 Applications 面板中的应用,即可进入详细监控视图。
3.3 快捷操作
-
F7:线程转储
-
F9:堆转储
-
Ctrl+Shift+S:CPU 采样
四、应用程序监控
4.1 本地应用监控
VisualVM 自动列出当前运行在本机上的 Java 应用,点击即可开始监控。
4.2 远程应用监控
-
菜单栏选择:
文件 -> 添加远程主机
-
输入 IP 地址
-
添加 JMX 端口连接即可。
4.3 示例:连接并监控 Tomcat 服务
假设 Tomcat 启动命令中已配置 JMX 参数,在 VisualVM 中添加远程主机并配置 9010 端口,即可开始实时监控。
五、性能分析功能
5.1 CPU 分析
VisualVM 提供两种方式进行 CPU 分析:采样(Sampling) 和 分析(Profiling)。
5.1.1 CPU 采样
-
优点:低开销,适合线上问题快速诊断。
-
使用方式:点击"Sampler"面板中的"CPU"按钮,然后点击"启动"按钮即可开始。
-
示例:
public class CPUDemo {
public static void main(String[] args) {
while (true) {
Math.pow(Math.random(), Math.random());
}
}
}
查看哪些方法调用最频繁,定位性能热点。
5.1.2 CPU 分析
-
优点:提供方法调用图(调用路径),适用于深入剖析。
-
缺点:性能开销较大。
-
启用方式:点击"Profiler"面板中的"CPU",点击"分析",程序开始执行分析。
5.2 内存分析
VisualVM 可分析对象的分配情况,定位内存泄漏。
5.2.1 采样
-
查看不同类实例的分布与大小。
-
确定内存分配热点类。
5.2.2 分析(Profiling)
-
启动方式同 CPU 分析,选择 Memory 分析项。
-
查看哪些类在短时间内创建大量对象。
5.3 线程分析
通过"Threads"面板,VisualVM 提供实时线程视图:
-
查看每个线程的当前状态(如 RUNNABLE、WAITING)
-
查看线程调用栈,排查死锁。
示例死锁检测:
Thread t1 = new Thread(() -> {
synchronized (String.class) {
synchronized (Integer.class) {
System.out.println("t1 done");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (Integer.class) {
synchronized (String.class) {
System.out.println("t2 done");
}
}
});
t1.start(); t2.start();
使用 VisualVM 查看线程栈时可清晰发现死锁位置。
六、堆转储与分析
在 Java 应用的性能分析与故障排查过程中,内存泄漏是最常见的问题之一。VisualVM 提供了堆转储(Heap Dump)功能,帮助开发者捕获并分析 JVM 堆内存的使用情况。
6.1 什么是堆转储(Heap Dump)
堆转储是 JVM 内部堆内存的快照,包含了所有在特定时刻仍被引用的对象信息。通过分析堆转储文件,我们可以发现:
-
哪些类实例数量最多
-
哪些对象占用了最多内存
-
是否存在无法释放的对象(潜在内存泄漏)
6.2 如何生成堆转储
在 VisualVM 中生成堆转储非常简单:
-
打开已连接的 Java 应用程序。
-
在应用标签中点击顶部菜单的 堆转储(Heap Dump) 按钮,或使用快捷键 F9。
-
VisualVM 会在内存分析面板中展示生成的堆转储数据。
堆转储也可以通过外部工具生成,例如使用 jmap
命令:
jmap -dump:live,format=b,file=heapdump.hprof <pid>
然后使用 VisualVM 打开 .hprof
文件进行分析。
6.3 堆转储分析界面详解
VisualVM 的堆转储分析面板主要包括以下几个部分:
-
Classes:列出所有类及其实例数、内存占用等。
-
Instances:可查看每个类的对象实例,并进一步查看其字段。
-
References:追踪对象引用关系,判断 GC Root 路径。
6.4 内存泄漏排查实战
示例代码:内存泄漏
import java.util.ArrayList;
public class LeakDemo {
static final ArrayList<Object> list = new ArrayList<>();
public static void main(String[] args) {
while (true) {
list.add(new byte[1024 * 1024]); // 每次添加 1MB
}
}
}
运行以上代码后,在 VisualVM 中观察内存曲线持续上升。
-
生成堆转储并查看
byte[]
实例数量和总大小。 -
查看引用链,发现所有对象都被
LeakDemo.list
静态字段引用,无法被 GC。
6.5 分析技巧与建议
-
使用 类名过滤器 快速定位目标类。
-
关注"大量实例"或"单个对象占用巨大空间"的类。
-
检查
GC Root
路径,识别不必要的强引用。 -
多次堆转储对比分析,观察增长趋势。
6.6 限制与注意事项
-
堆转储会暂停目标 JVM,在线业务需谨慎操作。
-
堆转储文件体积大,不宜频繁保存。
-
分析大量数据时可能消耗大量内存,建议使用 64 位 JVM 运行 VisualVM。
七、线程转储与分析
线程问题是 Java 应用中常见的性能瓶颈之一,尤其是在出现死锁、线程饥饿、线程暴涨等异常行为时,线程转储(Thread Dump)是定位问题的重要手段。VisualVM 提供了便捷的线程转储功能,并配套可视化分析工具,帮助开发者快速理解并解决线程相关问题。
7.1 什么是线程转储(Thread Dump)
线程转储是 JVM 某一时刻所有线程的执行状态快照,包括线程名称、线程 ID、线程状态(如 RUNNABLE、WAITING、BLOCKED)、调用堆栈等信息。通过线程转储我们可以:
-
分析线程运行状态
-
定位死锁位置
-
检查是否存在线程等待/阻塞异常
-
查看线程栈顶方法,排查性能热点
7.2 如何生成线程转储
在 VisualVM 中生成线程转储的方式如下:
-
打开目标应用程序。
-
点击"线程(Threads)"选项卡。
-
点击工具栏上的"线程转储"按钮,或使用快捷键 F7。
也可以使用命令行工具 jstack
获取线程转储:
jstack <pid> > threaddump.txt
然后使用 VisualVM 打开或配合外部工具进行分析。
7.3 线程分析界面详解
在 VisualVM 中点击"Threads"标签页后,可以看到如下内容:
-
实时线程列表:展示当前所有线程及其状态。
-
线程活动图表:以图形方式展示线程数量变化趋势。
-
线程栈跟踪:显示选中线程的调用栈详情。
-
死锁检测提示:若存在死锁,VisualVM 会自动在界面顶部显示警告并高亮相关线程。
7.4 死锁分析实战
示例:典型死锁代码
public class DeadlockDemo {
private static final Object A = new Object();
private static final Object B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (A) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (B) {
System.out.println("Thread 1 completed");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (B) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (A) {
System.out.println("Thread 2 completed");
}
}
});
t1.start();
t2.start();
}
}
运行该程序后:
-
使用 VisualVM 打开线程面板
-
生成线程转储并查看两个线程的状态
-
可发现
Thread-1
持有锁 A 等待锁 B,而Thread-2
持有锁 B 等待锁 A -
VisualVM 自动检测到死锁并显示红色提示
7.5 排查线程阻塞与等待
在实际系统中,有时线程并未死锁,但可能长时间处于 BLOCKED 或 WAITING 状态,例如等待数据库连接、等待锁竞争。
通过 VisualVM 可:
-
观察线程的状态及变更频率
-
分析线程栈顶位置(是否频繁阻塞在某个方法)
-
排查是否出现连接池枯竭、I/O 阻塞等问题
7.6 分析技巧与建议
-
多次采集线程转储,分析线程栈变化趋势
-
使用线程名称筛选关键业务线程(如
http-nio-8080-exec-XX
) -
检查是否存在大量状态为 BLOCKED 的线程,查找锁资源竞争点
-
对比不同时间点的线程活动变化,识别异常增长趋势
7.7 注意事项
-
在线程较多的系统中,线程转储信息可能非常庞大,建议使用筛选与搜索工具进行辅助分析。
-
线程转储为静态快照,建议结合持续监控图表观察长期趋势。
-
若 VisualVM 无法检测死锁,可结合
jstack
与第三方分析器(如 fastThread)辅助诊断。
八、插件与扩展
VisualVM 的强大功能不仅源于其内置工具,还得益于灵活的插件架构。通过安装插件,用户可以扩展 VisualVM 的功能,满足更复杂的性能分析、可视化需求。
8.1 插件中心简介
VisualVM 提供内建的插件中心(Tools > Plugins),其中包含了多个官方和社区提供的插件。用户可以在不重启工具的前提下安装、更新或卸载插件。
打开方式:
-
在菜单栏选择 Tools > Plugins。
-
浏览 Available Plugins 选项卡。
-
勾选所需插件,点击"安装"即可。
8.2 常用插件推荐
以下是一些实用的官方或社区插件,推荐安装:
- VisualGC
-
实时显示 JVM 的各个堆区(Eden、Survivor、Old Gen、Perm/Metaspace)使用情况。
-
适合观察 GC 活动与内存分布。
- BTrace 插件
-
支持动态注入 Java 字节码,无需重启应用。
-
可用于无侵入地追踪变量值、方法调用。
-
示例:打印某类某方法的入参和执行时间。
- MBeans 插件(JMX)
-
展示应用的 MBean 树结构。
-
支持读取、修改属性值,调用操作方法。
- Sampler Diff
- 对比不同时间点采样结果,识别性能变化趋势。
- Threads Visualizer(线程可视化)
- 提供线程状态动态时间线视图,便于观察线程生命周期及状态变化。
8.3 插件安装与使用示例
以安装 VisualGC 插件为例:
-
打开插件管理器:
Tools > Plugins
-
在"可用插件"中选择 VisualGC
-
点击"安装",按提示完成
-
安装后重新打开 Java 应用节点,会多出一个
VisualGC
标签页
该标签页中会实时显示各堆区容量与使用量,辅助分析 GC 频率与内存压力。
8.4 开发自定义插件(高级)
VisualVM 允许开发者基于其模块架构(NetBeans Platform)自定义插件。
基本步骤:
-
使用 NetBeans IDE 创建
Module Suite
项目。 -
引入 VisualVM API:
- 使用 Maven 或直接引用 SDK Jar 包。
-
实现核心扩展接口:如
ApplicationPlugin
,TabProvider
,DataSourceView
等。 -
编译后生成
.nbm
文件,在 VisualVM 中通过"Downloaded"安装。
插件功能示例:
-
实现自定义的性能仪表盘
-
监控特定业务指标,如缓存命中率、请求成功率等
-
创建日志分析面板,实时展示异常堆栈
参考文档:
-
VisualVM 官方插件开发文档:https://visualvm.github.io/plugins.html
-
NetBeans 平台开发指南:https://netbeans.apache.org/kb/docs/platform.html
8.5 插件使用注意事项
-
插件安装后可能需重启 VisualVM 才能生效
-
插件兼容性受限于 VisualVM 版本,请定期检查更新
-
避免同时安装多个资源密集型插件,以防性能下降
九、实用示例
本章通过几个实际案例展示如何使用 VisualVM 快速定位 Java 应用的性能问题、资源泄漏和线程异常。通过这些示例,读者可以掌握 VisualVM 在真实场景中的应用技巧。
9.1 示例一:定位内存泄漏问题
背景
一个长期运行的服务应用突然频繁发生 OutOfMemoryError
,并且系统响应变慢。
分析步骤
-
使用 VisualVM 连接目标应用。
-
打开 "Monitor" 面板,观察堆内存使用曲线是否呈持续上升趋势。
-
点击 "Heap Dump" 生成堆转储。
-
切换到 "Heap Dump" 视图:
-
查看 "Classes" 标签,按 "Instances" 或 "Size" 排序。
-
查找是否存在异常多的对象实例。
-
-
使用 "References" 查看对象引用链,识别 GC 无法回收的路径。
示例代码(模拟泄漏)
import java.util.*;
public class MemoryLeakDemo {
static List<Object> leakList = new ArrayList<>();
public static void main(String[] args) throws Exception {
while (true) {
leakList.add(new byte[1024 * 100]); // 每次添加 100KB
Thread.sleep(100);
}
}
}
运行该程序后观察内存曲线变化,可明显发现内存持续增长且不被回收。
9.2 示例二:排查线程死锁
背景
线上应用偶尔无响应,经排查为线程死锁。
分析步骤
-
使用 VisualVM 打开目标应用。
-
点击 "Threads" 面板,观察是否存在大量 BLOCKED 线程。
-
点击 "Thread Dump" 查看线程调用栈。
-
如果检测到死锁,VisualVM 会高亮冲突线程。
示例代码(模拟死锁)
public class DeadlockExample {
private static final Object A = new Object();
private static final Object B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (A) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (B) {
System.out.println("Thread 1 done");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (B) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (A) {
System.out.println("Thread 2 done");
}
}
});
t1.start();
t2.start();
}
}
执行后立即使用 VisualVM 查看线程状态,即可发现死锁。
9.3 示例三:分析 CPU 高占用
背景
系统突然 CPU 占用飙升,但线程数正常。
分析步骤
-
在 VisualVM 中点击目标应用。
-
打开 "Sampler" > "CPU",点击 "Start" 开始采样。
-
等待采样一段时间后点击 "Stop",查看方法调用热点。
-
找出占用 CPU 时间最长的方法。
示例代码(模拟 CPU 紧张)
public class CpuHotLoop {
public static void main(String[] args) {
while (true) {
Math.pow(Math.random(), Math.random());
}
}
}
启动该程序后用 VisualVM 采样,可以看到 Math.pow
是 CPU 消耗最多的热点方法。
9.4 示例四:监控远程服务器应用
背景
需要实时监控部署在远程 Linux 上的 Java 应用。
操作步骤
-
确保远程应用启动时开启了 JMX 支持,例如:
java -Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=<remote-ip>
-jar app.jar -
在 VisualVM 中添加远程主机:右键 "远程" > "添加主机"
-
输入 IP 地址后,右键该主机 > 添加 JMX 连接
-
成功连接后即可像本地应用一样进行采样、监控、转储等操作