☰
JVM 线上排查实战(七):jps 看不到进程、jstack 报不允许的操作怎么办?attach 失败的六种情况实测
2026/10/8 2:05:21 网站建设 项目流程

这个系列

「JVM 线上排查实战」第七篇。前六篇教的所有命令,都有一个前提:工具能连上那个进程。

  1. (一)先把 JVM 看清楚:进程、参数、默认值
  2. (二)CPU 飙高:找到那个线程
  3. (三)线程卡住:死锁、BLOCKED、线程池打满
  4. (四)内存:OOM 了先干什么
  5. (五)GC 日志:从 JDK 8 升 17,老启动参数会让进程直接起不来
  6. (六)JFR 飞行记录器:录一段现场下来慢慢看
  7. (七)工具连不上进程时怎么办—— 本篇

这一篇把「连不上」的几种情况逐个构造出来,贴每一种的原始报错,并给出还能用什么。

实测环境: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.Jps

root 的三个进程连影子都没有。不是权限报错,是干脆不显示 —— 这也是「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.Jps

5739 没了。但进程活得好好的,按 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 JfrDemo

jstack:

$ /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 17jstackJDK 8 进程✅ 正常输出(Full thread dump … 25.381-b09)
JDK 8jstackJDK 17 进程✅ 正常输出(Full thread dump … 17.0.8+9-LTS-211)
JDK 17jcmdJDK 8 进程✅ 正常输出
JDK 8jmap -heapJDK 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:+DisableAttachMechanismkill -3,从进程日志里读栈
手边 JDK 版本和进程对不上工具走 attach 还是 SAjstack/jcmd可以跨版本;jmap -heap不行
jstack -F在 17 上报错—jhsdb jstack --pid <pid>
什么工具都连不上启动脚本有没有把 stdout 丢掉kill -3+ 看进程自己的日志

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询