你有没有过这样的瞬间:线上服务告警了,你想用 Arthas 快速看一眼方法调用、线程栈或 GC 情况,于是满怀信心地敲下java -jar arthas-boot.jar。结果等了两秒,终端上出现一行冷冰冰的提示:Can not find java process. Try to pass <pid> in command line.。更诡异的是,有时列表是空的,有时明明ps -ef能看到那个 java 进程,Arthas 却把它当空气。
Arthas 作为国内使用率最高的 Java 在线诊断工具,遇到“启动时无法获取 java 进程”几乎是每个人都会撞上的第一道坎。这个问题的根源并不在 Arthas 本身,而在 JVM 的 attach 机制、系统权限、容器隔离甚至 JDK 版本之间的协同关系上。这篇文章把我这些年排查这个问题的思路、命令、踩坑记录整理出来,希望能帮你尽快定位原因,少走弯路。
1. 别急着怪 Arthas:先弄懂它“找进程”的底层机制
1.1 attach 模型是“临时敲门”,agent 模式是“提前入住”
Arthas 有两种工作方式,搞清楚它们是理解问题的前提。
第一种是 attach 模式,这也是 arthas-boot 默认的行为。Arthas 启动时,通过 JDK 自带的 Attach API 去枚举本机 JVM 进程,拿到 PID 列表后让你选择。选完之后,Arthas 会 attach 到目标 JVM,把 agent 注入进去,再通过 telnet 或 websocket 跟你交互。整个过程像临时喊一个专家上门维修:你得知道门牌号(PID),得让门卫放行(attach 机制),还得拿对钥匙(系统权限和用户身份)。
第二种是 agent 模式,在 JVM 启动前显式加上-javaagent:/path/to/arthas-agent.jar。应用一启动,agent 就已经加载好了,后续直接通过端口连接。这种模式下根本不存在“找进程”的问题,因为你相当于在装修时就把诊断仪器预埋进了房子里。
所以你要先搞清楚:你现在用的是哪种方式?如果是在应用启动后临时诊断,基本都走 attach 模式;“无法获取 java 进程”的报错,也几乎都发生在 attach 模式下。
1.2 报错常见的三种形态,先对号入座
同样是“无法获取 java 进程”,实际报错形态不太一样。我把日常遇到的情况归成三类:
| 报错形态 | 典型提示 | 最可能的原因 |
|---|---|---|
| 列表为空 | Can not find java process. Try to pass <pid> in command line. | 用户身份不一致、tmpdir 被修改、jps 本身也看不到 |
| 选中进程后失败 | Attach to process 12345 failed, error: ... | 权限不足、容器 seccomp 限制、DisableAttachMechanism、tmpdir 不可写 |
| 启动直接异常 | NoClassDefFoundError: com/sun/tools/attach/VirtualMachine | 运行 Arthas 的环境是 JRE,缺 JDK 的 attach 模块 |
第一类最容易让人困惑,因为你会想:明明服务器上有 java 进程,凭什么说找不到?第二类更贴近“进程看到了,但进不去”。第三类就比较直白了,属于环境本身不完整。
先把报错对号入座,接下来每一步排查才有方向。
2. 第一道分水岭:jps 能看到的,Arthas 不一定列出;jps 看不到的,Arthas 一定找不到
2.1 jps 能看到但列表为空:版本与运行环境的锅更大
遇到这种问题,我第一反应不是查权限,而是先跑一条命令:
jps -l如果jps -l能清楚列出目标 Java 进程,而 Arthas 的列表却是空的,那问题通常出在 Arthas 自身环境上,比较常见的有两种:
第一种是 Arthas 版本太旧。早期版本在 JDK 9+ 的模块化环境下,用 jps 或者 attach API 枚举进程时兼容性并不好,列表偶尔会出现漏进程的情况。这种问题没什么好说的,直接升级到最新版本,或者至少 3.6.0 以上的稳定版。
第二种是 Arthas 运行时用的 JDK 和目标任务不是同一个。服务器上装多个 JDK 的情况太常见了。java -jar arthas-boot.jar用的是 PATH 里默认的 java,而目标进程可能跑在另一个 JDK 上。当两个 JDK 的 attach 协议或 hsperfdata 文件格式有差异时,就可能出现“目标进程明明活着,Arthas 却列不出来”的诡异现象。解决办法是显式指定 JAVA_HOME:
export JAVA_HOME=/usr/local/jdk8 java -jar arthas-boot.jar如果你用的是as.sh脚本,脚本本身会尝试自动探测,也可以手动指定JAVA_HOME后再执行。
还有一个容易被忽略的场景:目标 Java 进程跑在 OpenJ9 或一些特殊 JVM 上。OpenJ9 对 hsperfdata 机制的支持和 HotSpot 不完全一样,jps 偶尔会漏,Arthas 枚举时也会跟着漏。这种情况下别耗在列表上,直接用后面第 4 节的方法指定 PID 强连。
2.2 连 jps 都看不到:用户、tmpdir 和启动方式的盲区
如果jps -l本身就看不到目标进程,那就不是 Arthas 的锅了,问题出在 JVM 进程可见性层面。我建议按下面的顺序逐一排查。
先看用户身份。JVM 枚举进程时,会去读取临时目录下的性能数据文件,这个目录按用户名隔离,通常是/tmp/hsperfdata_<username>。普通用户 A 看不到用户 B 启动的 Java 进程,这在多账号服务器上非常常见。你拿 root 登录,目标进程是 appuser 起的,jps -l大概率看不到。验证方式很简单:
ps -ef | grep java | grep -v grep sudo -u appuser jps -lps看到的是操作系统层面的进程,jps看到的是“当前用户视角下的 Java 进程”,两者并不等价。ps能看到但jps看不到,多半就是用户隔离。
再看 tmpdir。如果目标进程启动时加了-Djava.io.tmpdir=/data/tmp,那么它的 hsperfdata 文件也会跑到/data/tmp下。jps 默认从/tmp找,自然找不到。你可以用下面的方式验证:
ls -la /data/tmp/hsperfdata_* /tmp/hsperfdata_* 2>/dev/null如果确认 tmpdir 被改了,jps 可以用-J-Djava.io.tmpdir=/data/tmp临时指定:
jps -J-Djava.io.tmpdir=/data/tmp -l但这种“换个目录找”的思路在 Arthas 里不好直接落地,所以更务实的做法是记住目标 PID,直接走强连方案。
另外还有一种情况:进程是用javaw(Windows)或某些服务包装器启动的,它们可能不创建标准 hsperfdata 文件,导致 jps 和 Arthas 都看不到。这类进程往往比较特殊,排查时不要纠结列表,直接指定 PID。
3. 权限与容器的隐形边界:为什么 root 反而连不上普通用户起的进程
3.1 attach 的 UID 校验:切到同一个用户再说
很多人问我:我都用 root 了,为什么还连不上 appuser 启动的 Java 进程?
这里得说清楚一个关键点:枚举进程和 attach 进程,对权限的要求不一样。枚举阶段可能只是读文件,root 有权限读,所以能看到;但 attach 阶段需要向目标 JVM 发信号、创建本地 socket 文件并建立通信,HotSpot 的 attach 机制会严格校验调用方 UID 是否与目标进程 UID 一致。不一致就直接拒绝,root 也不例外。
所以在生产环境排障,我基本不会用 root 去 attach 服务账号的进程,而是先切用户:
sudo -u appuser java -jar arthas-boot.jar或者指定 PID:
sudo -u appuser java -jar arthas-boot.jar 27654如果你不确定目标进程是哪个用户起的,用一条命令确认:
ps -o user,pid,cmd -p 27654看到 user 那一列,再用sudo -u <用户名>去执行 Arthas,大部分“找不到进程”的问题在这一步就能解决。
macOS 上还有一层额外的坑。如果你是在本机调试,Java 进程由图形界面会话启动,而你在终端里用另一个用户身份运行 Arthas,attach 会因为权限或 session 隔离失败。这种情况下,直接 sudo 运行或者确保终端的登录用户和 Java 进程的启动用户一致。
3.2 Docker/K8s 里的 seccomp 与 PID namespace 坑
容器环境是重灾区,而且报错花样最多。
最常见的是:docker exec进容器,运行 arthas-boot,能看到 Java 进程,但一 attach 就报Attach to process ... failed。这个现象十有八九是 Docker 默认的 seccomp 配置把ptrace系统调用拦了。JVM 的 attach 机制在 Linux 上依赖和信号、socket 文件、进程跟踪相关的系统调用,没有 SYS_PTRACE 权限就白搭。
解决办法是在启动容器时放开权限:
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it my-service如果你用 docker-compose,可以这样写:
security_opt: - seccomp:unconfined cap_add: - SYS_PTRACE到 K8s 里,对应的配置是 securityContext:
securityContext: capabilities: add: ["SYS_PTRACE"]另外一个坑是容器内只有 JRE 没有 JDK。很多基础镜像为了瘦身,只装 JRE。Arthas 在 JDK 8 下依赖 tools.jar,在 JDK 9+ 下依赖jdk.attach模块,这些只有完整 JDK 才有。容器里跑 Arthas 时如果报类找不到、模块找不到,先检查一下java -version和 JAVA_HOME 指到哪里。要么把 JDK 挂载进容器,要么换一个自带 JDK 的诊断镜像。
还有 PID namespace 的影响。你进入容器后看到的 PID 1,跟宿主机上看到的 PID 可能是两回事。如果你在宿主机上运行 arthas-boot,默认是看不到容器里的 Java 进程的;反过来,在容器里也看不到宿主机的进程。所以操作前先想清楚,你现在的执行环境到底在哪个 namespace 里。如果确实需要在宿主机上诊断某个容器内的 Java 进程,容器启动时要加--pid=host,并且放开 ptrace 权限,否则别费劲了,直接进容器操作更省事。
4. 绕过列表:直接指定 PID 强连的完整姿势
4.1 指定 PID 的各种启动参数组合
列表获取有问题,最直接的办法就是绕过列表,手动指定 PID。Arthas 本来就支持这个姿势:
java -jar arthas-boot.jar 27654用as.sh也一样:
./as.sh 27654如果目标机器有多个网卡、多个 Java 进程,或者你想固定端口方便防火墙放行,可以把 telnet 端口和 http 端口一起指定:
java -jar arthas-boot.jar --telnet-port 3658 --http-port 8563 27654这里有个小细节:PID 必须是目标 Java 进程的 PID,不是容器 PID,也不是调用方的 PID。如果你是在容器里操作,执行:
jps -l看到的就是容器视角的 Java PID,用它就行。
指定 PID 还有一个好处:省掉了交互选择的那一步,在脚本化、自动化排障时非常有用。比如你想快速抓一下某个服务的线程栈,直接写:
./as.sh 27654 -c "thread -n 3"这些话可以在非交互模式下执行,配合报警系统做自动化诊断很合适。
4.2 PID 也连不上时,按这个顺序往下查
指定 PID 仍然失败的场景,我遇到过不少。这时候不要慌,按下面的顺序逐项验证。
第一步,确认 attach 功能本身没被禁用。有些安全加固脚本会给 JVM 加上-XX:+DisableAttachMechanism,这个参数一加,jstack、jmap、jcmd、Arthas 全部失效。验证方式:
jinfo -flag DisableAttachMechanism 27654如果输出是-XX:-DisableAttachMechanism说明没禁用;如果输出是-XX:+DisableAttachMechanism或命令本身报错,那问题就锁定了。
第二步,用 jstack 做个基线测试。Arthas 本质上也是通过 Attach API 加载 agent,所以 jstack 能不能用,可以作为判断 attach 是否正常的快速探针:
jstack -l 27654如果 jstack 也失败,说明问题在 JVM attach 层面,不是 Arthas 的锅。如果 jstack 成功而 Arthas 失败,那就回头查 Arthas 版本、缓存、日志。
第三步,检查 tmpdir 权限和磁盘空间。attach 需要在临时目录下创建.java_pid<pid>socket 文件,如果/tmp被写满,或者权限被改得很死,attach 一样会失败。可以看:
df -h /tmp ls -ld /tmp第四步,翻 Arthas 的日志。Arthas 会在~/.arthas/logs下记录详细日志,里面通常能看到 attach 失败的具体异常堆栈:
tail -n 100 ~/.arthas/logs/arthas.log日志里的异常信息非常直白,比如well-known file is not secure指向权限问题,Unable to open socket file指向 tmpdir 或用户问题。
第五步,清理 Arthas 缓存重试。Arthas 首次运行会从远程拉取依赖,缓存在~/.arthas目录。缓存损坏也可能导致各种奇怪问题:
rm -rf ~/.arthas java -jar arthas-boot.jar 276544.3 终极兜底:agent 模式与提前埋点
如果 attach 实在连不上,而你又必须在这个进程上做诊断,那就只剩一条路:等待重启窗口,在 JVM 启动参数里显式加上 Arthas agent。
java -javaagent:/opt/arthas/arthas-agent.jar -jar your-app.jar加了之后,应用启动时就会加载 Arthas agent,随后你可以直接通过 telnet 连接 3658 端口。这种方式从源头绕开了“启动时找进程”的问题,非常适合那种安全加固特别严格、禁止动态 attach 的环境。
我见过不少团队把这一招固化到发布流程里:如果服务对诊断能力是刚需,就在启动脚本里默认带上 agent,或者至少保留一个可切换的配置项。问题真正来临时,你不需要在凌晨两点和 attach 权限搏斗,直接连端口就行。
不过要注意,agent 模式也有成本:Arthas agent 会占用一定内存和性能,通常在低负载场景下问题不大,但高并发核心链路要评估一下。我的建议是,要么在预发环境加 agent 做充分压测,要么平时不启用,只在发布时通过环境变量动态注入。
5. 一次真实排障复盘:从空列表到成功 attach 的全过程
5.1 场景描述与第一步命令
去年有一次线上订单服务 CPU 飙到 90%+,我登录服务器打算用 Arthas 看线程。当时犯了一个典型错误:直接用 root 执行了java -jar arthas-boot.jar,结果列表空荡荡,连自己差点都想骂人。
我马上用ps确认目标进程还活着:
ps -ef | grep order-service输出里有目标 PID 18342,进程用户是 orderuser。问题基本清晰了,这是典型的用户隔离。
5.2 逐步缩小范围的四个验证
第一步,切到目标用户验证 jps 视角:
sudo -u orderuser jps -l输出里出现了 18342,确认进程在 orderuser 的 JVM 视角下是可见的。
第二步,直接用目标用户运行 Arthas:
sudo -u orderuser java -jar arthas-boot.jar 18342这次不再报“找不到进程”,而是换了新错误:Attach to process 18342 failed。说明问题从进程可见性转移到了 attach 权限层。
第三步,用 jstack 探测:
sudo -u orderuser jstack -l 18342结果也失败。这时候我已经怀疑启动参数里加了禁 attach 的标志。查一下进程完整启动参数:
ps -ef | grep 18342 | grep -v grep cat /proc/18342/cmdline | tr '\0' ' '果然看到了-XX:+DisableAttachMechanism和-Djava.io.tmpdir=/var/tmp两个关键参数。前者直接禁止了所有动态 attach,后者把 hsperfdata 挪到了非默认位置。这两个参数叠加,Arthas 想找到进程都难。
第四步,验证 tmpdir 的影响。用 jps 指定 tmpdir 再看:
sudo -u orderuser jps -J-Djava.io.tmpdir=/var/tmp -l18342 确实在列表里,说明 tmpdir 修改确实影响了枚举逻辑。
5.3 最终结论与后续加固
结论是:这个进程被安全加固策略锁死了,Arthas 在进程运行期间根本无法 attach。想现场诊断,最靠谱的办法就是重启窗口内移除-XX:+DisableAttachMechanism,并在启动参数里显式加上 agent 埋点。
我们没有为了诊断而立刻重启线上服务,而是先用了 jstack 的替代手段绕过?其实 jstack 也依赖 attach,也不可用。最后我们改用实时日志和监控曲线先顶住,等发布窗口把启动参数修正后,再在脚本里预留了-javaagent:/opt/arthas/arthas-agent.jar,下一次启动就自带诊断能力了。
这个案例最有价值的教训是:看似是 Arthas 的问题,根源却是启动参数和账号体系。排查时不要围着 Arthas 转,先看 JVM 层面的可见性和可 attach 性。
6. 日常预防:我踩过几次坑后固化的几条规矩
6.1 上线前10秒能做完的三项自检
与其每次都在线排障,不如上线前顺手做几件很小的事,真的能省下大把时间。
第一,别手痒加-XX:+DisableAttachMechanism。有些安全扫描建议加,但如果你们的运维和开发需要在线诊断,这就是自己给自己上锁。如果必须要加,请确定团队里有替代方案,比如提前埋 agent。
第二,明确服务启动账号。在部署文档里写清楚“服务由 appuser 启动,诊断命令必须用sudo -u appuser执行”。这一条能直接解决 80% 的“列表空白”问题。
第三,检查 tmpdir 是否有被改写的习惯。如果中间件或安全策略要求自定义java.io.tmpdir,那就要知道它对 jps 和 Arthas 的影响。最好在部署脚本里加一条注释,标注自定义 tmpdir 的路径,排障时少走弯路。
容器和 K8s 环境,还要额外确认镜像里是否有完整 JDK,以及 runtime 是否放开了 SYS_PTRACE。这些配置看起来是“安全收紧”,但对 Java 诊断链路的破坏是致命的。
6.2 线上紧急介入时的速查清单
真到了线上出问题、手忙脚乱的时候,有一个备查清单非常重要。我自己的习惯是按下面这个顺序来:
# 1. 确认目标进程和启动用户 ps -ef | grep java | grep -v grep # 2. 切到同一用户看 jps 视角 sudo -u <user> jps -l # 3. jps 看不到时检查 tmpdir 和 hsperfdata ls -la /tmp/hsperfdata_* /var/tmp/hsperfdata_* 2>/dev/null # 4. 检查禁用 attach 参数 jinfo -flag DisableAttachMechanism <pid> # 5. 用 jstack 做 attach 基线测试 sudo -u <user> jstack -l <pid> # 6. 直接指定 PID 启动 Arthas sudo -u <user> java -jar arthas-boot.jar <pid>如果第 5 步 jstack 都失败,就直接跳过 Arthas 的挣扎,去查启动参数和容器权限。如果 jstack 成功但 Arthas 失败,再去清理 Arthas 缓存、升级版本、翻日志。
这套清单看起来简单,但每次都能把问题边界划得很清楚:到底是你“看不见进程”,还是“看得到但进不去”。这两类的排查路径完全不同。
最后分享一个个人心得:Arthas 这类工具能不能用,表面看是工具问题,本质上是 JVM 生态的透明度和控制权问题。把账号、权限、tmpdir、容器能力这些底层约束想清楚,你不仅能解决“启动找不到进程”,还能举一反三,应对 jstack、jmap、jcmd 连不上的各种衍生问题。把这些经验沉淀成团队的 trouble-shooting 手册,比每次临时百度强太多了。