
VisualVM这个工具很多人第一次见到它是在JDK安装目录的bin文件夹里双击打开后看到一堆本地Java进程却不知道接下来能做什么。我用它排查过不少诡异问题——CPU莫名其妙飙高、堆内存只涨不跌、线程池集体卡死VisualVM就是那个能让你“看见”JVM内部状态的工具。这篇文章我不会讲太多理论就按实际排查问题的顺序把启动、本地监控、堆Dump、远程连接、常见坑一条龙讲透让你看完就能上手用起来。1. VisualVM到底是什么先记住这四个核心用途在开始操作之前得先搞清楚VisualVM能解决什么问题。它不是压测工具不是日志分析工具也不负责帮你调优参数它更像一辆“JVM体检车”把Java进程运行时最关键的几项健康指标拉到你面前让你用肉眼就能看到异常。1.1 一张表看懂它能监控哪些指标很多刚接触的人被一堆面板吓住其实核心就四个维度CPU、内存、线程、类加载。我整理成一张表你对照着看就知道每项指标背后对应什么问题。监控维度你能看到什么主要用来排查什么CPU每个线程的CPU占用、热点方法调用频率线程死循环、频繁GC、CPU飙高堆内存Eden/Survivor/Old区的实时占用、GC次数与耗时内存泄漏、Full GC频繁、对象分配压力元空间Metaspace区的加载量动态生成类过多导致的OOM线程线程状态、线程栈、死锁检测线程阻塞、锁竞争、线程泄漏类加载已加载类数量、每个ClassLoader加载的类动态代理/反射异常、类卸载失败用生活类比的话CPU面板相当于看一个人时刻在忙什么线程面板是看他在排队还是在干活堆内存面板是看仓库里堆了多少货、货还剩多少地方能放类加载面板是看仓库里办了多少张出入证。1.2 它和JConsole、Arthas这些工具有什么区别很多人会问有JConsole不就行了吗为什么还要单独学VisualVM我实际用下来的感受是JConsole能做基础监控但功能太薄想导出堆Dump、分析线程栈、做CPU采样都捉襟见肘更像一个只能看体温的温度计。VisualVM相当于一台便携B超机除了基础监控还能做堆Dump、线程转储、CPU采样、插件扩展覆盖面广操作又是图形化点一点就行学习成本低。Arthas命令行在线诊断神器能反编译、能直接改运行中的逻辑功能更猛但上手门槛高适合已经知道问题大概方向之后去做精准定位。所以我的建议是日常巡检、初步排查、快速定位“是不是JVM层面的问题”用VisualVM最合适等发现问题细节需要在线干预时再上Arthas也不迟。这篇文章只讲VisualVM因为它足以覆盖80%的日常诊断场景。2. 启动与连接5分钟跑通本地进程监控VisualVM的获取和使用门槛很低但有几个细节容易被新手忽略我一步步说清楚。2.1 VisualVM从哪里获取和启动最省事的方式是直接用JDK自带的那个版本。打开你的JDK安装目录找到bin文件夹里面有个jvisualvmWindows下是jvisualvm.exeLinux/macOS下是jvisualvm脚本双击就能跑起来。不过我建议你去VisualVM的官方开源页面下载最新版。JDK自带的版本通常较旧插件中心和部分新特性支持得不好新版原生支持更高版本的JDK、界面更流畅还能安装更多插件。注意如果你用的操作系统是macOS首次启动可能会提示“已损坏”这是因为没有经过应用商店公证去“系统设置-隐私与安全性”中允许运行即可。启动后左侧的Applications面板会列出一堆正在运行的本地Java进程默认按PID显示格式类似25892 com.example.DemoApplication。双击某个进程右侧会打开一个包含概览、监控、线程、采样器、Profiler等标签页的详情界面。2.2 界面布局与第一个操作第一次打开详情页你会看到“概览”标签里面展示启动参数、系统属性、JVM参数等信息。在这里可以快速确认进程启动时加了哪些JVM参数比如-Xmx、-XX:UseG1GC等。切到“监控”标签就能看到CPU、堆内存、元空间、类加载数的实时曲线图。图表右下角有“执行垃圾回收”和“堆Dump”按钮这两个按钮后面会频繁用到。我的建议是刚启动的进程先观察5分钟让曲线稳定下来再根据业务特点判断哪些指标是正常的。比如一个服务刚启动时堆内存迅速增长是正常的因为要加载类、初始化连接池但如果一路涨到顶部还不回落就要小心了。2.3 本地进程“消失”了怎么办有个场景特别常见你给某个JVM配置了特殊启动参数比如-Xmx4g结果VisualVM的进程列表里就是看不到它。这时候先打开命令行输入jps -l看看进程是否真的在跑。如果jps能看到、VisualVM看不到多半是权限问题——比如你是用普通用户启动的工具而目标进程是用root起的VisualVM可能无法读取进程信息。还有一类情况是容器场景。如果Java程序运行在容器里你从宿主机启动VisualVM看到的可能只有宿主机上的Java进程看不到容器内的进程。这种情况下建议直接在容器内装一个VisualVM或者用后面要讲的远程JMX连接方式把监控端口暴露出来从宿主机连进去。3. 本地监控实战CPU飙升、堆内存异常、线程阻塞怎么查工具说白了只是看得见真正有价值的是怎么从曲线漂移里判断出JVM生了什么病。这一节我按三类高频问题来讲CPU高、堆内存异常、线程阻塞。全程用的是实际排查过的场景细节略作调整。3.1 CPU采样找到真正吃CPU的方法CPU飙高是线上最常遇到的问题。如果你面对的是一个正在运行的Java进程最好的方式不是猜而是让VisualVM的“采样器”跑一段时间的CPU采样它会按线程和方法维度告诉你CPU时间花在哪里。操作路径是打开进程详情页切到“采样器”标签点击“CPU”按钮等待10到30秒然后点击“停止”VisualVM会生成一个采样快照按线程展示每个方法的采样次数和CPU时间占比。这里要注意一个常见误解采样器显示的是“采样命中次数”不是精确的CPU时间。虽然大部分情况下命中率能反映真实热点但分配内存比较多、短小轻快的方法容易在采样中被低估。所以看到结果后不要急着改代码至少要结合线程栈确认热点方法是否有明显的循环或大对象分配行为。举一个实际碰过的例子某个定时任务系统在每分钟整点CPU冲到90%以上采了几轮样都指向同一个XML解析方法。顺着方法往下看发现代码在遍历一个超大节点集合时反复调用getElementsByTagName每次都重新遍历整棵DOM树。把遍历改成一次性缓存之后整点峰值直接降到20%。这类问题不用压测工具也能找到VisualVM的采样器够用了。3.2 堆内存面板看懂新生代、老年代和元空间在“监控”标签下你会看到堆内存曲线。默认展示的是总堆、Used、Eden、Survivor、Old、Metaspace等各区域的占用情况。很多新手只看总堆占用觉得内存还有剩余就没问题这是不对的。GC的工作原理是多数对象先在Eden区分配Minor GC之后存活对象进入Survivor区多次存活之后升入Old区。所以你观察曲线时要注意几点Eden区曲线频繁升起又回落说明Minor GC频繁通常是短生命周期对象创建太多。Old区曲线只涨不跌说明有对象长期存活且持续增长大概率是内存泄漏或缓存没有淘汰机制。Full GC频繁Old区满之后触发Full GC曲线会大幅下降但很快就又涨回来这种“锯齿状”曲线很危险通常离OOM不远。我接手过一个批处理服务Old区每10分钟就涨上去触发一次Full GC每次Full GC要停顿2秒多业务方反馈任务跑得越来越慢。用VisualVM一看Old区曲线像心电图一样规律波动。配合堆Dump一查发现问题是一个全局Map缓存只put不清理key还是按时间生成的永不过期。这就是典型的“看起来堆还有空间实际老年代已经病入膏肓”的情况。在监控标签页可以直接点击“执行垃圾回收”按钮手动触发一次GC。这个操作在诊断时很有用——如果手动GC之后Old区能降下来说明对象还是可回收的不是泄漏如果降不下来那基本可以断定存在泄漏。3.3 线程面板死锁、阻塞、锁等待一条条看切到“线程”标签页VisualVM会显示所有存活线程的状态分为Running、Sleeping、Wait、Park等几种。点开任一线程能看到它的完整调用栈这几乎是排查线程问题的万能钥匙。我在实际中最常看的两个点第一Blocked状态的线程多不多。Blocked意味着线程在等锁大量线程Blocked往往说明锁竞争激烈可以继续点开看是卡在哪个方法上。如果是synchronized块方法名会直接显示在栈顶附近如果是java.util.concurrent.locks会显示park相关栈帧。第二死锁检测。VisualVM提供了自动死锁检测功能在线程面板中如果检测到死锁会在顶部出现“检测到死锁”的提示并列出形成死锁的线程和它们各自持有的锁。我用这个方法抓到过一个“循环等待锁”的经典案例两个线程各自持有对方需要的锁谁也不让谁。直接看线程栈能一眼发现互相等待的关系修复方式就是统一加锁顺序。还有一个容易被忽略的线程数量持续上涨但不下降这通常意味着线程池没做回收或者有线程泄漏。在VisualVM的概览里可以看到存活线程数我见过一个系统的线程数从几百慢慢爬到几千最后直接把内存拖垮。这种问题在VisualVM里表现非常明显——线程数曲线一路向北然后堆内存也跟着一路向北。3.4 实际案例一个定时任务和一次Full GC的排查过程我自己遇到过最具代表性的一个场景是一个“白天正常凌晨4点CPU飙高”的定时任务系统。刚开始以为是定时任务本身逻辑复杂后来凌晨4点盯着VisualVM看发现CPU曲线瞬间冲高采样器定位到热点是一个Excel导出工具的内部方法。继续看线程栈发现这个定时任务在导出时每行数据都要走一次反射映射几千行数据叠加起来反射开销直接吃满CPU。换成预编译的Bean映射、去掉多余的反射字段之后任务执行时间从20分钟缩到3分钟CPU峰值也降到正常水平。另一个案例是大促期间某服务的Full GC特别频繁每次Full GC之后线上接口RT就会“抖”一下。用VisualVM远程连上生产环境后发现堆内存曲线呈锯齿状老年代涨满就Full GCFull GC之后就下降。结合线程栈和堆Dump分析定位到一个大对象在业务代码里被引用到静态变量业务接口每次调用都会往里面塞数据而静态变量又没有清理机制。真正展开来看就是一行代码的事但不借助VisualVM根本发现不了。4. 堆Dump与内存泄漏定位老年代涨满之前抓个现行如果你已经怀疑存在内存泄漏单纯看曲线还不够必须拿到堆Dump去做证据链分析。VisualVM在这个环节非常顺手但很多细节值得说透。4.1 如何生成和打开Heap Dump生成堆Dump有几种方式各有适用场景。VisualVM本身的按钮最简单进程详情页的“监控”标签右下角有“堆Dump”按钮点击就会生成一个.hprof文件并自动打开分析视图。这种方式适合进程还能接受停顿、内存不太大的情况因为Dump需要暂停JVM并复制堆数据。如果进程已经OOM或者马上要OOM再打开VisualVM点按钮可能来不及。正确的做法是提前在JVM启动参数里加上-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/heapdump.hprof这样OOM发生时JVM会自动把当时的堆快照写到指定路径事后用VisualVM打开分析。这是生产环境标配参数强烈建议所有Java服务都加上成本极低收益极大。如果进程还活着但VisualVM连不上也可以用命令行工具jmap -dump:formatb,file/tmp/heap.hprof pid或者用JDK 8都支持的jcmdjcmd pid GC.heap_dump /tmp/heap.hprof4.2 最常用的OQL查询打开堆Dump后VisualVM默认进入“概述”视图能看到每个类的实例数量、Shallow Heap对象自身占用、Retained Heap对象及被引用的子孙对象总占用。Retained Heap是判断内存大头最直观的指标一般先从Retained Heap最大的类入手。真正的高阶操作是OQL查询。VisualVM内置了OQL类似SQL的查询语法可比IDEA的Heap Viewer或MAT更直接。举几个常用的查询# 查询某个类的所有实例 select * from com.example.MyService # 按实例数量排序 select className, count(*) from heap.objects group by className order by count(*) desc实际排查中我用OQL做过一个特别实破的查询某个大对象的实例数是业务应该创建数量的10倍顺着引用链一看是一个长度为1的线程池在for循环里反复创建旧对象没有被释放全部堆积在新生代撑到老年代。这个如果不看OQL光猜半天也猜不到。4.3 一个泄漏案例的操作过程我举一个“看Dump找根因”的完整步骤你照这个流程走就行发现Old区只涨不跌Full GC频率上升到每分钟一次。在VisualVM中点击“堆Dump”等待生成完成。切到“概述”标签按Retained Heap排序找到占比最大的那个类。右键点击类名选择“查看实例”然后点其中一个实例选择“引用”。在引用视图中看谁持有了这个实例一路追到GC Roots。我记得有一次追到一个对象发现是被某个任务队列的任务引用着仔细看这个任务又引用了整条业务数据链导致整个“火车车厢”都被扣住无法回收。定位到具体代码后发现是任务处理完没有清理上下文变量。代码一改Full GC频率降到一天几次接口RT恢复稳定。注意堆Dump文件通常很大动辄几百MB甚至GB打开时VisualVM也会吃不少内存。如果Dump文件特别大建议在VisualVM中通过菜单“文件-装载”打开并给VisualVM自身调大-Xmx。另外Dump时JVM会暂停一段时间生产环境操作一定要选业务低峰期。5. 远程JVM监控不折腾生产线用jstatd和JMX连上去本地能监控已经很方便但真正的难点在“生产环境怎么连”。VisualVM提供了两种远程连接方式一种是轻量的jstatd另一种是功能更完整的JMX。我强烈建议你在测试环境先练熟再上生产。5.1 jstatd方式轻量查看适合测试环境jstatd是JDK自带的远程监控服务启动它之后远端主机会暴露一个RMI服务VisualVM通过这个服务读取JVM的监控数据。优点是配置简单缺点是只能看监控数据不能做线程转储、堆Dump操作。在目标机器上先创建一个安全策略文件jstatd.policy内容如下grant codebase file:${java.home}/../lib/tools.jar { permission java.security.AllPermission; };然后启动jstatdjstatd -J-Djava.security.policy/path/to/jstatd.policy如果只想让特定主机访问还可以加上-J-Djava.rmi.server.hostname本机内网IP。启动后在VisualVM中右键“Applications”面板里的“远程”选择“添加远程主机”输入目标IP然后右键这个主机选择“添加jstatd连接”就能看到远程JVM的监控数据了。不过jstatd的局限性很明显很多生产环境出于安全考虑不会跑这种服务而且它默认使用RMI的随机端口防火墙配置起来有点烦。所以正式环境我更推荐JMX。5.2 JMX方式正式环境更完善的监控通道JMXJava Management Extensions是Java平台标准的监控管理机制VisualVM的连接方式就是在启动参数中打开JMX端口然后在本地添加JMX连接。这种方式和Spring Boot Actuator这类监控框架不冲突反而能相互补充。目标JVM的启动参数推荐这样配-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port9010 -Dcom.sun.management.jmxremote.rmi.port9010 -Dcom.sun.management.jmxremote.authenticatetrue -Dcom.sun.management.jmxremote.sslfalse -Dcom.sun.management.jmxremote.local.onlytrue -Djava.rmi.server.hostname目标机器内网IP这里有个很关键也最容易踩坑的参数-Dcom.sun.management.jmxremote.rmi.port9010。JMX本身打开一个9010端口但RMI还会随机开一个数据端口用于回传数据。如果不固定这个RMI端口防火墙就只能开9010但数据连接走随机端口必然连不上。把rmi.port和jmxremote.port设成同一个端口防火墙只需要放行这一个端口即可。另一个坑是hostname。如果目标机器的/etc/hosts没有把本机IP正确映射到主机名RMI返回给客户端的地址会是乱走的导致连接失败或超时。在启动参数里显式指定Djava.rmi.server.hostname可以彻底规避这个问题。连接方式也很简单VisualVM中右键“远程”选择“添加远程主机”输入IP后右键主机选择“添加JMX连接”输入IP:9010然后填入jmxremote认证所需的用户名密码如果开启了认证就能看到完整的监控面板。JMX方式支持采集、堆Dump、线程转储等操作比jstatd强太多。5.3 RMI端口不通的排查思路远程连接最常遇到的错误就是连接超时或拒绝连接。我总结了排查链条按顺序排查基本能解决先确认目标进程确实监听了端口用netstat -anp | grep 9010看端口是否在LISTEN状态。从本机用telnet IP 9010测端口通不通。不通就去检查防火墙规则和云安全组。通的话还是连不上重点检查java.rmi.server.hostname配置。RMI回传的地址不对VisualVM收到一个非法地址自然连不上。如果配置了authenticate确认账号密码文件权限正确JMX的密码文件需要设置为当前用户只读。权限过大时JVM会拒绝启动或认证失败。我在地域跨机房连接时踩过一次坑A机房的VisualVM连B机房的JVM始终超时telnet端口又是通的。后来排查发现目标机器有多个网卡RMI随机端口走了另一个网卡的IP防火墙只放行了业务网卡端口才导致数据通道被拒。配上hostname参数后问题直接消失。6. 常见问题速查与我的避坑技巧最后把我在实际使用中遇到的高频问题整理成一张速查表都是能直接落地的解决方案。问题现象可能原因解决办法本地进程列表为空权限不足或进程是容器内运行的用jps确认进程换同一系统用户或改用JMX远程连接远程主机添加成功但监控数据不刷新jstatd版本与JDK不一致确保jstatd和业务进程使用同一JDK版本JMX连接超时RMI随机端口未固定加上-Dcom.sun.management.jmxremote.rmi.port并放行该端口连接被拒绝认证文件权限过大或hostname配置错误设置密码文件权限600显式指定hostname堆Dump文件打不开文件太大导致VisualVM内存溢出调大VisualVM的-Xmx或改用MAT分析CPU采样结果不稳定采样时间太短或JVM在频繁GC延长采样到30秒以上避开GC高峰期插件中心无法加载网络到更新源不通去官方插件站点手动下载放到VisualVM的plugin目录后离线安装老年代曲线锯齿状Full GC频繁及时做堆Dump分析优先排查Retained Heap大的对象避坑技巧我按优先级列几个远程连接认证一定要开。生产环境暴露一个不带认证的JMX端口等于把JVM的操作权限直接送给内网任何一台机器这是致命的。开authenticate并不难但很多人图省事就关了风险极大。观察数据要选业务高峰期。有些指标比如线程数、堆内存占用在低峰期看着完全正常到了高峰期才会暴露问题。所以我做定期巡检时会刻意挑业务流量大的时段去看VisualVM的实时曲线。采样器和Profiler不要同时开。同一个进程上同时跑采样和Profiler会双倍影响性能还可能让采样数据失真。一次只用一个工具诊断完立刻停止。不要只看VisualVM一个工具就下结论。把jstat命令、jmap命令、jstack命令的输出和VisualVM的曲线放一起交叉验证结论才可靠。比如VisualVM显示CPU高用top -Hp 看线程级CPU也是一层验证。最后说一个自己总结的用法遇到JVM相关的怪问题我会先开VisualVM看整体健康度确认“CPU、堆、线程、类加载”哪个维度异常然后针对异常维度做深入采样或Dump分析再结合业务代码定位根因。整个流程走下来大部分问题都能在半小时内找到方向。VisualVM不是万能的但它作为第一梯队的诊断工具能帮你少走很多弯路非常值得花点时间练熟。