这个系列
「JVM 线上排查实战」第七篇。前六篇教的所有命令,都有一个前提:工具能连上那个进程。
- (一)先把 JVM 看清楚:进程、参数、默认值
- (二)CPU 飙高:找到那个线程
- (三)线程卡住:死锁、BLOCKED、线程池打满
- (四)内存:OOM 了先干什么
- (五)GC 日志:从 JDK 8 升 17,老启动参数会让进程直接起不来
- (六)JFR 飞行记录器:录一段现场下来慢慢看
- (七)工具连不上进程时怎么办—— 本篇
这一篇把「连不上」的几种情况逐个构造出来,贴每一种的原始报错,并给出还能用什么。
实测环境:CentOS 7.9.2009(内核3.10.0-1160.71.1.el7.x86_64),同机三个 JDK —— Oracle1.8.0_381(/opt/jdk8)、OpenJDK1.8.0_412(/usr/lib/jvm/java-1.8.0-openjdk)、Oracle17.0.8(/usr/java)。测试程序是一个死循环烧 CPU + 不停分配对象的小程序。
先说结论
jps看不到,不代表进程有问题,更不代表你连不上它。jps靠的是/tmp/hsperfdata_<用户>/<pid>这个文件,而 attach 靠的是另一套东西 —— 三种情况下jps空着但jcmd <pid>照样能用- 换个用户就看不见了:
appuser跑jps只看得到自己那一个,root 的三个进程一个都不显示 - 普通用户 attach root 的进程,报的是「不允许的操作」,不是「找不到进程」
- 进程被
kill -STOP之后,jstack和jcmd在限时内都不返回(60 秒 / 30 秒,退出码 124),kill -CONT之后立刻恢复正常 -XX:+DisableAttachMechanism是最坑的一种:jps里还看得见这个进程,但所有 attach 工具全废- 跨版本 attach 要看工具走哪条路:
jstack/jcmd(走 attach)两个方向都能用;jmap -heap(走 SA)跨版本直接抛异常 jstack -F在 JDK 17 上已经没了,报错会告诉你改用jhsdb jstack- 最后一条后路是
kill -3:线程栈打到进程自己的标准输出里,连 attach 被禁用时它都还能用
1.jps看不到进程:三种原因 ✅
1.1 你和进程不是同一个用户
root 下看,四个 Java 进程都在:
$ /usr/java/bin/jps -l 5842 JfrDemo 6706 jdk.jcmd/sun.tools.jps.Jps 6679 JfrDemo 5737 JfrDemo 5739 JfrDemo切到appuser(它自己跑了其中一个,pid 6679),同一条命令:
$ su - appuser -c '/usr/java/bin/jps -l' 6679 JfrDemo 6727 jdk.jcmd/sun.tools.jps.Jpsroot 的三个进程连影子都没有。不是权限报错,是干脆不显示 —— 这也是「jps看不到进程」最常见的原因:你用普通账号登录,而服务是另一个账号起的。
反过来root 能看到所有用户的(上面那个 6679 就是 appuser 的)。
1.2/tmp下的 hsperfdata 文件被清掉了
jps的数据来源是这个目录:
$ ls /tmp/hsperfdata_root/ 5737 5739 5842一个文件对一个 pid。很多机器上有定时清理/tmp的任务,把它删掉之后:
$ rm -f /tmp/hsperfdata_root/5739 $ /usr/java/bin/jps -l 5842 JfrDemo 6679 JfrDemo 5737 JfrDemo 6829 jdk.jcmd/sun.tools.jps.Jps5739 没了。但进程活得好好的,按 pid 直接用jcmd一点问题都没有:
$ /usr/java/bin/jcmd 5739 VM.uptime 5739: 323.039 s🔑jps空 ≠ 连不上。这一条能省掉很多无谓的折腾:从ps -ef | grep java里拿到 pid,照样往下查。
1.3 启动参数里关掉了UsePerfData
$ /usr/java/bin/java -XX:-UsePerfData -cp c17 JfrDemo这个进程(pid 6894)在jps里同样是不存在的:
$ /usr/java/bin/jps -l | grep -c "^6894 " 0而jstack照样连:
$ /usr/java/bin/jstack 6894 2026-09-23 22:47:46 Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.8+9-LTS-211 mixed mode, sharing):2.jstack报「不允许的操作」✅
用appuser去 attach root 的进程:
$ su - appuser -c '/usr/java/bin/jstack 5739' 5739: 不允许的操作(英文环境下是Operation not permitted。)
反过来root 去 attach 普通用户的进程,可以:
$ /usr/java/bin/jstack 6679 2026-09-23 22:47:16 Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.8+9-LTS-211 mixed mode, sharing):→attach 要么同用户,要么用 root。看到「不允许的操作」先看ps -ef | grep <pid>第一列是谁。
3. 进程被暂停了:命令挂在那儿不返回 ✅
kill -STOP把进程挂起(线上等价的情况是进程卡在不可中断状态、或者被调试器停住):
$ kill -STOP 5842 $ timeout 60 /opt/jdk8/bin/jstack 5842 > stop8.txt 2>&1; echo "jstack 退出码=$? 输出 $(stat -c%s stop8.txt) 字节" jstack 退出码=124 输出 0 字节124是timeout杀掉它的退出码 ——60 秒内jstack没有返回任何东西。换jcmd也一样:
$ timeout 30 /opt/jdk8/bin/jcmd 5842 Thread.print > stopc.txt 2>&1; echo "jcmd 退出码=$? 输出 $(stat -c%s stopc.txt) 字节" jcmd 退出码=124 输出 6 字节那 6 个字节只是它先打的一行5842:,后面什么都没有 ——看起来像是在输出,其实已经卡住了。
恢复之后立刻就正常:
$ kill -CONT 5842 $ timeout 30 /opt/jdk8/bin/jstack 5842 > cont8.txt 2>&1; echo "退出码=$? 输出 $(stat -c%s cont8.txt) 字节" 退出码=0 输出 3836 字节🔑attach 是要目标进程自己配合的(它要起一个 Attach Listener 线程来响应)。进程被停住 = 没人接你的电话。所以jstack敲下去半天没反应时,先看ps -o stat= -p <pid>:T就是被停住了。
4. 最坑的一种:jps看得见,但谁都连不上 ✅
启动参数里带了-XX:+DisableAttachMechanism(有些安全加固基线会要求加它):
$ /usr/java/bin/java -XX:+DisableAttachMechanism -cp c17 JfrDemojstack:
$ /usr/java/bin/jstack 7138 7138: The VM does not support the attach mechanism [退出码=1]jcmd:
$ /usr/java/bin/jcmd 7138 Thread.print 7138: com.sun.tools.attach.AttachNotSupportedException: The VM does not support the attach mechanism at jdk.attach/sun.tools.attach.HotSpotAttachProvider.testAttachable(HotSpotAttachProvider.java:154) [退出码=1]而jps里它还好端端地列着:
$ /usr/java/bin/jps -l | grep -c "^7138 " 1🔑这就是为什么不能拿jps判断"工具能不能用":前面 1.2 / 1.3 是「jps看不见但连得上」,这里正好反过来,是「jps看得见但连不上」。这两件事测的根本不是同一样东西。
这种情况下,下面第 6 节那条后路仍然有效。
5. 跨版本 attach:能不能用,取决于工具走哪条路 ✅
同一台机器上有 JDK 8 和 17 的进程,手边的工具却只有一个版本时:
| 用谁的工具 | 打谁的进程 | 结果 |
|---|---|---|
JDK 17jstack | JDK 8 进程 | ✅ 正常输出(Full thread dump … 25.381-b09) |
JDK 8jstack | JDK 17 进程 | ✅ 正常输出(Full thread dump … 17.0.8+9-LTS-211) |
JDK 17jcmd | JDK 8 进程 | ✅ 正常输出 |
JDK 8jmap -heap | JDK 17 进程 | ❌ 抛异常 |
jmap -heap那次的原文:
$ /opt/jdk8/bin/jmap -heap 5739 Attaching to process ID 5739, please wait... Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498)🔑分界在工具走哪条路:jstack/jcmd走的是 attach 协议(让目标进程自己打印),对版本宽容;jmap -heap走的是 Serviceability Agent,要按目标 JVM 的内存结构去解析,版本对不上就炸。
这也解释了(四)里那个现象:
jmap -heap在 17 上要换成jhsdb jmap --heap --pid。
jstack -F在 JDK 17 上没了
JDK 8 上-F还在(它走的也是 SA):
$ /opt/jdk8/bin/jstack -F 5737 Attaching to process ID 5737, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.381-b09 Deadlock Detection:JDK 17:
$ /usr/java/bin/jstack -F 5739 Error: -F option used Cannot connect to core dump or remote debug server. Use jhsdb jstack instead→17 上把jstack -F换成jhsdb jstack --pid <pid>。
6. 最后那条后路:kill -3✅
所有 attach 工具都用不了的时候,还有一个办法:给进程发SIGQUIT,JVM 会把线程栈打到自己的标准输出里。
$ ls -la d17.log # 发信号前 17 字节 $ kill -3 5739 $ ls -la d17.log # 发信号后 7401 字节 $ grep -c 'Full thread dump' d17.log 1内容和jstack是一样的东西:
Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.8+9-LTS-211 mixed mode, sharing): Threads class SMR info: _java_thread_list=0x00007fc8240028c0, length=16, elements={连第 4 节那个 attach 被禁用的进程,kill -3照样拿得到:
$ kill -3 7138 da.log 17 -> 5534 字节 $ grep -c 'Full thread dump' da.log 1⚠️ 两个前提:
- 它打到的是进程的标准输出,也就是你启动脚本里重定向的那个文件(
nohup.out、catalina.out、xxx.log)。如果启动时把 stdout 丢进了/dev/null,那这条路也断了 ——这是启动脚本该现在就去改的一件事 kill -3不会杀死进程(SIGQUIT被 JVM 接管了),但别对kill -9抱同样的期待,那个是真杀
7. 顺带说清 attach 到底靠什么文件 ✅
attach 用的是/tmp下的一个 socket 文件,名字是.java_pid<pid>:
$ ls /tmp/.java_pid* /tmp/.java_pid5737 /tmp/.java_pid5739 /tmp/.java_pid5842 /tmp/.java_pid6679 …它不看java.io.tmpdir。启动时指定-Djava.io.tmpdir=/root/tmpx的进程(pid 7253),attach 一样成功:
$ /usr/java/bin/jstack 7253 > td_jstack.txt 2>&1; echo "退出码=$? 输出 $(stat -c%s td_jstack.txt) 字节" 退出码=0 输出 5446 字节而 socket 文件仍然落在/tmp,指定的那个目录是空的:
$ ls -a /root/tmpx . ..顺带一个细节:/tmp下能看到早就退出的进程留下的.java_pid文件(实测里有.java_pid77783这种,进程早没了)。所以别拿/tmp/.java_pid<pid>在不在来判断进程是否健在,那只是个残留文件。
8. 本篇速查
| 现象 | 先查什么 | 还能用什么 |
|---|---|---|
jps什么都不显示 | 你和进程是不是同一个用户(ps -ef | grep java第一列) | ps拿 pid →jcmd <pid>照样能用 |
jps少了某个进程 | /tmp/hsperfdata_<用户>/下有没有那个 pid 文件;启动参数有没有-XX:-UsePerfData | 同上,按 pid 直接连 |
<pid>: 不允许的操作 | 进程属主是谁 | 切成同一个用户,或用 root |
| 命令敲下去不返回 | ps -o stat= -p <pid>是不是T(被 STOP) | kill -CONT恢复后再查;或kill -3看日志 |
The VM does not support the attach mechanism | 启动参数有没有-XX:+DisableAttachMechanism | kill -3,从进程日志里读栈 |
| 手边 JDK 版本和进程对不上 | 工具走 attach 还是 SA | jstack/jcmd可以跨版本;jmap -heap不行 |
jstack -F在 17 上报错 | — | jhsdb jstack --pid <pid> |
| 什么工具都连不上 | 启动脚本有没有把 stdout 丢掉 | kill -3+ 看进程自己的日志 |