JVisualVM本地监控零配置,自动列出进程;远程需JMX参数配置;堆曲线波动属正常GC行为;死锁检测限于synchronized;CPU高且RUNNABLE多为计算密集型。

JVisualVM 能直接连上本地 Java 进程看 CPU、堆、线程,但远程监控默认不支持,得手动配 JMX;本地用它基本零配置,比 jstat + jstack 拼凑直观得多。
怎么快速连上本地正在跑的 Java 进程
启动 jvisualvm(通常在 JDK 的 bin/ 目录下),打开后左侧“本地”节点会自动列出所有本机运行的 Java 进程(基于 tools.jar 和 Attach API)。只要进程没加 -XX:+DisableAttachMechanism,就能点开看实时数据。
常见卡点:
- 用不同 JDK 启动的进程,可能不会出现在对应 JDK 版本的 JVisualVM 里(比如用 JDK 17 启的程序,用 JDK 8 的 jvisualvm 就看不到)
- IDE 内嵌的 JVM(如 IntelliJ 的后台进程)有时被过滤掉,可尝试勾选顶部菜单栏「工具 → 选项 → 常规 → 显示所有进程」
- 如果只看到
jvisualvm自身进程,检查是否禁用了 Attach:查启动参数有没有-XX:+DisableAttachMechanism
为什么远程连接总是提示“连接失败:Connection refused”
JVisualVM 默认不支持远程直连,必须在目标 JVM 启动时显式开启 JMX 端口并放开权限。不是配个 IP 就能连。
立即学习“Java免费学习笔记(深入)”;
关键启动参数示例(JDK 8+):
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=192.168.1.100
注意:
-
java.rmi.server.hostname必须填远程机器的真实 IP(不能是localhost或127.0.0.1),否则 RMI 回连会失败 - Linux 上还要确认防火墙放行
9999端口,且 JVM 绑定的是0.0.0.0:9999而非127.0.0.1:9999 - 如果启用了认证(
authenticate=true),需额外配jmxremote.password和jmxremote.access文件,容易配错权限导致拒绝访问
“监视”标签里的堆内存曲线忽高忽低,是内存泄漏吗
不一定。JVisualVM 的“监视”页显示的是 GC 后的堆使用量(即 used heap after GC),不是实时分配量。每次 Young GC 后 Eden 区清空,曲线就会陡降;Full GC 后整个堆回收,又会明显回落。
更可靠的判断方式:
- 切到「VisualGC」插件页(需提前安装),看 Old Gen 使用量是否随时间持续缓慢上升
- 用「堆 Dump」功能抓一次快照,用「OQL」查可疑对象:
select s from java.lang.String s where s.count > 100000 - 对比两次间隔几分钟的堆 Dump,用「比较」功能看哪些类实例数/总大小增长最快
单纯看“监视”页的峰值,容易把正常缓存填充误判为泄漏。
线程页显示“死锁”但应用还在响应,怎么回事
这里的“死锁”是 JVisualVM 根据 ThreadMXBean.findDeadlockedThreads() 检测出的同步块级死锁(即多个线程互相持有对方需要的 monitor 锁)。但它不检测 java.util.concurrent 里的锁(如 ReentrantLock)、数据库锁、文件锁或分布式锁。
所以:
- 标红的死锁线程,基本可以确认是 synchronized 或 wait/notify 导致的,该查代码里嵌套同步块或条件等待逻辑
- 没标红 ≠ 没锁问题:线程长时间
WAITING在LockSupport.park,可能是ReentrantLock.lock()阻塞,得结合线程栈里具体调用链判断 - “CPU 时间”列数值极高但线程状态是
RUNNABLE,大概率在跑密集计算或死循环,不是锁问题
真正麻烦的从来不是连不上,而是连上了却看不懂堆趋势和线程状态之间的关系——比如 Old Gen 缓慢上涨的同时,恰好有几个线程长期卡在 SocketInputStream.read,这时候得怀疑是连接没释放,而不是内存泄漏本身。


















