jdk工具-VisualVM

一、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 安装步骤

  1. 访问官网下载页面,根据操作系统选择合适的版本。

  2. 解压缩安装包至本地目录。

  3. 启动方式:执行 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 远程应用监控

  1. 菜单栏选择:文件 -> 添加远程主机

  2. 输入 IP 地址

  3. 添加 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 中生成堆转储非常简单:

  1. 打开已连接的 Java 应用程序。

  2. 在应用标签中点击顶部菜单的 堆转储(Heap Dump) 按钮,或使用快捷键 F9

  3. 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 中生成线程转储的方式如下:

  1. 打开目标应用程序。

  2. 点击"线程(Threads)"选项卡。

  3. 点击工具栏上的"线程转储"按钮,或使用快捷键 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),其中包含了多个官方和社区提供的插件。用户可以在不重启工具的前提下安装、更新或卸载插件。

打开方式:

  1. 在菜单栏选择 Tools > Plugins

  2. 浏览 Available Plugins 选项卡。

  3. 勾选所需插件,点击"安装"即可。

8.2 常用插件推荐

以下是一些实用的官方或社区插件,推荐安装:

  1. VisualGC
  • 实时显示 JVM 的各个堆区(Eden、Survivor、Old Gen、Perm/Metaspace)使用情况。

  • 适合观察 GC 活动与内存分布。

  1. BTrace 插件
  • 支持动态注入 Java 字节码,无需重启应用。

  • 可用于无侵入地追踪变量值、方法调用。

  • 示例:打印某类某方法的入参和执行时间。

  1. MBeans 插件(JMX)
  • 展示应用的 MBean 树结构。

  • 支持读取、修改属性值,调用操作方法。

  1. Sampler Diff
  • 对比不同时间点采样结果,识别性能变化趋势。
  1. Threads Visualizer(线程可视化)
  • 提供线程状态动态时间线视图,便于观察线程生命周期及状态变化。

8.3 插件安装与使用示例

以安装 VisualGC 插件为例:

  1. 打开插件管理器:Tools > Plugins

  2. 在"可用插件"中选择 VisualGC

  3. 点击"安装",按提示完成

  4. 安装后重新打开 Java 应用节点,会多出一个 VisualGC 标签页

该标签页中会实时显示各堆区容量与使用量,辅助分析 GC 频率与内存压力。

8.4 开发自定义插件(高级)

VisualVM 允许开发者基于其模块架构(NetBeans Platform)自定义插件。

基本步骤:

  1. 使用 NetBeans IDE 创建 Module Suite 项目。

  2. 引入 VisualVM API:

    • 使用 Maven 或直接引用 SDK Jar 包。
  3. 实现核心扩展接口:如 ApplicationPlugin, TabProvider, DataSourceView 等。

  4. 编译后生成 .nbm 文件,在 VisualVM 中通过"Downloaded"安装。

插件功能示例:

  • 实现自定义的性能仪表盘

  • 监控特定业务指标,如缓存命中率、请求成功率等

  • 创建日志分析面板,实时展示异常堆栈

参考文档:

8.5 插件使用注意事项

  • 插件安装后可能需重启 VisualVM 才能生效

  • 插件兼容性受限于 VisualVM 版本,请定期检查更新

  • 避免同时安装多个资源密集型插件,以防性能下降


九、实用示例

本章通过几个实际案例展示如何使用 VisualVM 快速定位 Java 应用的性能问题、资源泄漏和线程异常。通过这些示例,读者可以掌握 VisualVM 在真实场景中的应用技巧。

9.1 示例一:定位内存泄漏问题

背景

一个长期运行的服务应用突然频繁发生 OutOfMemoryError,并且系统响应变慢。

分析步骤

  1. 使用 VisualVM 连接目标应用。

  2. 打开 "Monitor" 面板,观察堆内存使用曲线是否呈持续上升趋势。

  3. 点击 "Heap Dump" 生成堆转储。

  4. 切换到 "Heap Dump" 视图:

    • 查看 "Classes" 标签,按 "Instances" 或 "Size" 排序。

    • 查找是否存在异常多的对象实例。

  5. 使用 "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 示例二:排查线程死锁

背景

线上应用偶尔无响应,经排查为线程死锁。

分析步骤

  1. 使用 VisualVM 打开目标应用。

  2. 点击 "Threads" 面板,观察是否存在大量 BLOCKED 线程。

  3. 点击 "Thread Dump" 查看线程调用栈。

  4. 如果检测到死锁,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 占用飙升,但线程数正常。

分析步骤

  1. 在 VisualVM 中点击目标应用。

  2. 打开 "Sampler" > "CPU",点击 "Start" 开始采样。

  3. 等待采样一段时间后点击 "Stop",查看方法调用热点。

  4. 找出占用 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 应用。

操作步骤

  1. 确保远程应用启动时开启了 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

  2. 在 VisualVM 中添加远程主机:右键 "远程" > "添加主机"

  3. 输入 IP 地址后,右键该主机 > 添加 JMX 连接

  4. 成功连接后即可像本地应用一样进行采样、监控、转储等操作