拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Arthas找不到Java进程?排查思路与实战方案

Arthas找不到Java进程?排查思路与实战方案

Arthas 是我线上排查 Java 问题最常用的工具,没有之一。不管是 CPU 飙高、线程卡死,还是接口偶发超时,只要把java -jar arthas-boot.jar跑起来,基本都能在现场找到线索。但恰恰是这一步,很多朋友第一次执行就翻车,屏幕上甩出一句Can not find java process. Try to pass <pid> directly.,然后进程退出,留下你对着终端发呆。这个“Arthas 启动时无法获取 java 进程”的问题,看着简单,背后涉及的坑却不少:用户权限不一致、容器 PID 命名空间隔离、JDK 版本兼容、端口占用、/tmp目录异常等等。我这些年踩过不少类似的坑,也帮同事排查过很多次,今天就把完整的排查思路和实操方案整理出来,希望能帮你少走弯路。

1. 问题现象与根因速览

1.1 你的报错是哪一种

“无法获取 java 进程”并不是某一条固定的报错,而是一类现象的总称。我实际遇到的至少有三种不同表现,先对照一下你属于哪种。

第一种最常见,就是开头提到的那句Can not find java process. Try to pass <pid> directly.。这通常意味着 Arthas 启动脚本在自动扫描本机 Java 进程时,一个都没扫到,或者扫到了一批但都被过滤掉了。这个报错在没加-p参数时最容易出现,Arthas 会去识别当前环境里的 JVM,识别失败就直接退出。

第二种是connect to target vm failed。这个报错说明 Arthas 已经找到了目标进程,但 attach 连接被拒绝,或者建立连接的过程中出现了异常。常见原因包括目标 JVM 关闭了 attach 机制、当前用户没有权限、JVM 正处于异常状态等。这种报错比“找不到进程”更进一步,说明问题出在 attach 链路。

第三种不是启动阶段直接报错,而是 ArThas 启动成功了,但后续无法访问控制台,比如 telnet 3658 端口连不上,或者 HTTP 端口访问不了。这时候报错信息往往是Connection refused或者timeout,容易被误判为“启动失败”,实际是端口被占用、防火墙拦截、网络命名空间不通等问题。

这三种现象背后对应完全不同的排查路径。你如果只看“无法获取 java 进程”这几个字就照着网上的教程一顿操作,很可能用修 A 问题的方案去搞 B 问题,最后越弄越乱。

1.2 核心机制:Arthas 是怎么拿到 Java 进程的

要理解报错,得先知道 Arthas 启动时内部做了哪些事。Arthas 的启动脚本as.sh或者arthas-boot.jar里有一段自动扫描逻辑:如果你没有用-p参数指定 PID,它会遍历当前机器上所有进程,筛选出可识别的 Java 进程,然后列一个带序号的菜单让你选择。如果扫描结果为空,就会直接报 "Can not find java process"。

这一层扫描依赖的是 Linux 下的/proc文件系统,以及 JVM 在/tmp/hsperfdata_<user>/目录下生成的性能数据文件。Arthas 会读取/proc下的进程列表,找到启动文件名或命令行里包含java的进程,再判断能否作为 attach 目标。如果/proc不可读、Arthas 运行用户和目标 JVM 用户不一致、或者目标进程的启动名被改掉,扫描结果就会异常。

确认 attach 目标之后,Arthas 会通过 JDK 自带的com.sun.tools.attachAPI 连接到目标 JVM。这个连接是否成功,又受到用户权限、PTTY、容器 PID 命名空间、/tmp目录可写性、JVM 参数-XX:+DisableAttachMechanism、JDK 模块访问限制等多重因素影响。所以“无法获取 java 进程”这句话,其实是“进程发现失败”和“attach 建立失败”两层问题的统称,排查时务必要先分清楚是哪一层。

2. 排查思路:先确认进程真的存在且可 attach

2.1 用 jps 和 ps 验证进程状态

拿到“无法获取 java 进程”的报错时,我的第一步从来不是去看 Arthas 的配置,而是先自己确认目标 Java 进程到底还在不在。这听起来很基础,但很多人真的会跳过这一步,直接怀疑 Arthas 坏了。

我建议两条命令一起执行:ps -ef | grep java和jps -l。前一条命令查看的是操作系统视角下的进程列表,后一条查看的是 JVM 视角下的进程列表。两个命令的结果一对照,就能判断问题在操作系统层还是在 JVM 发现层。

举个例子,我遇到过一种情况:项目跑在 systemd 服务里,启动脚本把进程名改成了javaapplication,ps -ef能清楚看到这个进程,但jps -l的输出却是空的。原因是这个 JVM 启动时发生了一些异常,导致/tmp/hsperfdata_目录下没有生成对应文件,或者生成后权限不对。Arthas 自动扫描是依赖jps同类的机制来发现进程的,所以jps看不到,Arthas 自然也就看不到。

反过来,jps能看到进程,也不代表 Arthas 一定能 attach。jps只是读取进程的存活信息,而 attach 需要与 JVM 建立 socket 连接,这里面还有权限和环境的制约。所以我会把ps -ef和jps -l作为第一道筛子,先把“进程是否存在”和“JVM 是否可发现”这两个状态搞清楚。

2.2 三种典型场景导致 jps 看不到进程

如果ps -ef | grep java能看到进程,但jps -l看不到,通常跳出下面三种场景。

第一种是用户不一致。jps和 Arthas 运行在普通用户下,但 Java 进程是 root 启动的。此时/tmp/hsperfdata_root目录只有 root 能读,普通用户执行jps -l自然什么都看不到。这种情况在服务器上非常常见,运维同学用一个部署账号管理服务,但某些中间件或脚本用 root 拉起 Java 进程,诊断的时候就发现工具“瞎了”。

第二种是容器环境。你在宿主机上执行jps,看到的是宿主机 PID 命名空间里的进程。容器里运行的 Java 服务,在宿主机上也能看到进程,但它的/tmp目录和hsperfdata文件是隔离在容器命名空间里的,宿主机上的jps往往读不到容器内 JVM 的信息,输出为空非常正常。

第三种是jps自身的坑。比如 Linux 上/tmp目录被挂载为 noexec 或只读,jps创建临时 socket 失败;或者 JDK 版本较老,对某些进程名兼容性不好;再比如进程正处于大量 GC 或假死状态,也可能导致jps超时或读取不到。

我自己的排查习惯是:先ps -ef | grep java看进程在不在,再jps -l看 JVM 能不能被发现,再用ls -l /proc/<pid>/cwd看当前用户是否具备访问权限。这三步走完,八成以上问题都能定位到具体环节。

3. 常见原因逐个击破

3.1 权限问题:你不是我,我不能让你进来

Arthas attach 目标 JVM 时,要求 Arthas 的运行用户和 JVM 的启动用户一致,这是一条安全规则。如果服务是以root用户启动的,而你用deploy账号执行java -jar arthas-boot.jar,Arthas 可能在扫描阶段看到进程,但 attach 阶段会被拒绝,报错connect to target vm failed或Permission denied。

解决方式不复杂:切换到目标进程相同的用户再启动 Arthas。比如su - deploy -c "java -jar arthas-boot.jar",或者用sudo提权执行。我个人更倾向于用服务账号而不是 root 去执行 Arthas。因为你用 root 虽然能 attach 任何进程,但操作风险大,而且在审计上不好看。而且用 root 成功掩盖了潜在的权限配置问题,下次换回普通用户又傻眼。

确认当前 Java 进程属于哪个用户,可以用:

ps -o user,pid,cmd -p <pid>

如果进程是你自己起的,权限一般没问题。如果是在别人管理的机器上,先和对方确认账号,沟通成本不高,但能少走很多弯路。

3.2 容器环境:世界被分成两个 PID 空间

容器环境导致的 attach 问题,是近两年我遇到最多的一类。核心原因在于 PID 命名空间隔离。Arthas 运行时,要 attach 目标 JVM,两个进程必须处于同一个 PID 命名空间,或者至少拥有跨命名空间 attach 的权限。

你在宿主机上直接运行 Arthas,看到的是宿主机的进程表。容器内的 Java 进程在宿主机看来也只是一个独立的普通进程,虽然能看到,但由于命名空间隔离,“够不着”它内部的 attach 文件。即使你手动指定宿主机的 PID,attach 也常常失败,因为 JVM 的 attach 机制依赖/tmp/.java_pid<pid>文件和/proc/<pid>目录,这些在容器内外并不是完全互通的。

最稳妥的方案是:如果 Java 进程在容器内,就把 Arthas 的启动命令通过docker exec或kubectl exec送进同一个容器里执行。比如:

kubectl exec -it <pod-name> -- bash java -jar arthas-boot.jar -p 1

前提是容器里有java命令,且有足够的 shell 和工具。很多精简镜像没有 bash 也没有 jps,那就得先把 Arthas 包拷贝进去,再用容器里仅有的工具运行。还有一种更干净的方案是给 Pod 加一个 sidecar 容器,专门放 Arthas 和诊断工具,与主业务容器共享 PID 和网络命名空间,然后进入 sidecar 操作。这个方案对生产环境侵入性小,也方便统一管理。

3.3 JDK 版本与 Arthas 版本不兼容

Arthas 对 JDK 版本相当敏感。你用 JDK 6 跑老项目,很多新版本 Arthas 根本不支持,因为com.sun.tools.attachAPI 在新版本里变化比较大。反过来,如果你的项目已经跑在 JDK 17 及以上,Arthas 启动时可能因为模块系统访问限制,报Unable to access com.sun.tools.attach或者IllegalAccessError之类的错。

遇到这种情况,先确认当前 Arthas 版本对 JDK 的支持情况。Arthas 官方 README 里有兼容矩阵,JDK 6+ 基本都支持,但每个版本对新 JDK 的适配进度不同。我自己的经验是尽量把 Arthas 保持在较新的稳定版,如果项目偏偏很老,就固定一个历史版本别随意升级。为了追一个 bug 升级 Arthas,结果发现 attach 不上了,得不偿失。

还有一个隐蔽的坑:Arthas 启动脚本as.sh使用的java命令,可能来自 PATH 中的第一个 Java,而这个 Java 的版本未必和目标 JVM 一致。比如目标 JVM 是 JDK 11,但as.sh用的是 JDK 8,attach 时可能因为版本不匹配报NoSuchMethodError或者UnsupportedClassVersionError。解决办法是显式设置JAVA_HOME环境变量,让 Arthas 使用与目标 JVM 一致的 JDK。

3.4 系统架构与 Linux 发行版差异

Arthas 本身是 Java 工具,跨平台能力很强,但启动脚本里涉及一些 native 操作,比如解析/proc、执行ptrace系统调用,这些会受到系统架构和内核权限影响。比如在 Mac M1 上,如果用的是 ARM 架构的 JDK,Arthas 老版本可能没有为 ARM 做好适配,启动时报错。Linux 上 SELinux 或 AppArmor 也可能拦截ptrace操作,导致 attach 失败。

遇到这类问题,建议先看系统级日志。运行dmesg或journalctl -f观察是否有operation not permitted或 SELinux 拦截记录。如果是容器环境,还要看有没有给容器加SYS_PTRACE权限。Docker 默认会放行部分 ptrace 操作,但严格配置下可能被禁止。你可以用--cap-add=SYS_PTRACE或者修改安全上下文来放开。

另一个容易被忽略的点是/tmp目录的状态。Arthas attach 时需要写临时文件到/tmp,如果/tmp被挂载为 noexec、大小写不敏感、或者空间满了,attach 都会失败。我遇到过两次/tmp被临时文件占满导致 Arthas 启动失败的案例,清理完空间后一切恢复正常。所以排查时顺手检查一下df -h /tmp和mount | grep /tmp,成本极低,却能排除一个潜在的雷区。

3.5 端口冲突与 telnet 连接问题

Arthas 启动成功后,会开启两个端口:telnet 端口默认 3658,HTTP/WebSocket 端口默认 8563。如果这两个端口被占用,Arthas 可能无法启动,或者在启动后客户端连接不上。表现上和“无法获取进程”不同,但很多人也会归到这个分类下反馈。

检查端口占用用ss -lntp | grep 3658,如果看到有进程监听,可以换端口启动:

./as.sh -p 12345 --telnet-port 9999 --http-port 9998

如果你在容器里使用 Arthas,还要注意网络命名空间问题。从宿主机的telnet 127.0.0.1 3658访问容器内的端口,默认是访问不通的,除非你做了端口映射或使用 host 网络模式。Kubernetes 环境更复杂,不能直接用 Pod IP 访问端口,需要先kubectl port-forward或者进入 Pod 内部再访问。

4. 实操:手动指定 PID 启动 Arthas

4.1 获取 PID 的几种正确姿势

如果自动扫描一直不靠谱,最直接的办法就是告诉 Arthas 目标进程的 PID。获取 PID 的方法有很多,我用得最多的是这几种。

jps -l的输出简洁,能直接看到主类名:

jps -l

ps -ef | grep java能看到更详细的启动参数,方便通过服务名或 jar 包名识别进程:

ps -ef | grep java

pgrep -f也经常用,特别适合脚本场景:

pgrep -f 'your-service-name'

在 Kubernetes 环境里,先kubectl get pod拿到 Pod 名,再kubectl exec进去执行jps。注意容器内的 PID 和宿主机看到的 PID 并不一致,你在容器内拿到的 PID 只能在容器内使用。

有一个容易踩的坑:ps -ef看到的可能不止一个 Java 进程,比如环境里有多个服务或中间件。建议通过启动参数里的-jar xxx.jar或-Dspring.profiles.active等关键字来精确识别。拿不准的时候,可以用jcmd <pid> VM.version验证一下这个 PID 是不是 JVM 进程。

4.2 一键启动并验证

拿到 PID 以后,启动 Arthas 就很简单了。脚本版用:

./as.sh -p 12345

jar 包版用:

java -jar arthas-boot.jar --pid 12345

如果你已经知道默认端口被占用,可以在启动时指定新端口:

./as.sh -p 12345 --telnet-port 9999 --http-port 9998

启动成功后,控制台会输出类似The target process is 12345, arthas home is /tmp/arthas/...的信息,然后进入交互式终端。此时输入help能看到所有命令,我习惯先跑dashboard看一眼整体状态,再根据问题用thread、trace、watch等命令定位。

如果启动时想保留更详细的日志,加一个-v参数:

./as.sh -p 12345 -v

-v会输出更底层的信息,对定位启动失败很有帮助。

4.3 attach 失败时的回退方案

如果指定 PID 后依然报错,说明问题不在“进程发现”阶段,而是“attach 建立”失败了。此时我建议先用 JDK 自带的jcmd验证一下 JVM 的 attach 通道是否正常:

jcmd 12345 VM.version

如果jcmd能正常返回 JVM 版本信息,说明 attach 机制是通的,问题可能出在 Arthas 本身,比如版本兼容。如果jcmd也报错,那就要检查目标 JVM 是否设置了-XX:+DisableAttachMechanism,或者容器内是否缺失libattach.so,以及当前用户对/tmp目录是否有写权限。

有一种情况比较特殊:目标 JVM 处于安全降级状态,比如正在 Full GC 或者执行有损 dump,attach 请求可能暂时无响应。这时候不要急着杀进程,等一会儿再试,通常能恢复。如果 Java 进程本身已经假死,jcmd也无响应,那就只能另想办法,比如通过 JMX 或者进程内日志排查了。

5. 容器内 Arthas 的正确打开方式

5.1 容器内 attach 宿主进程:先想清楚命名空间

很多人问“能不能在宿主机上直接诊断容器里的 Java 服务”,这是个好问题,但答案往往让人失望。虽然容器内进程在宿主机上也能看到,但 attach 的底层机制依赖文件系统和 socket,跨 PID 命名空间时会出现各种不一致。你在宿主机上jps能看到进程,但 Arthas attach 时可能找不到目标,或者连上了却无法正常加载 agent。

所以最省心的方案是进容器再诊断。Docker 环境用:

docker exec -it <container> bash

Kubernetes 环境用:

kubectl exec -it <pod> -- bash

进入容器后,用ps -ef或jps -l找到进程,再运行 Arthas。但很多基础镜像为了瘦身,只有 JRE 没有 JDK,连jps都没有。这种情况下你可以先把 Arthas 的包拷贝进容器,前提是容器里有java命令。

还有一个更轻量的方式:用nsenter进入目标进程的 PID 命名空间,然后在宿主机上执行 Arthas,让它看到容器内的进程。这个命令对权限要求高,一般需要 root 或 CAP_SYS_ADMIN,操作起来比较繁琐,不如直接进容器简单。

5.2 用 sidecar 模式解决跨容器诊断问题

如果业务容器太瘦,连进去也跑不了 Arthas,另一个方案是 sidecar。在 Kubernetes Pod 里,可以给某个 Pod 增加一个调试容器,与主业务容器共享网络命名空间和 PID 命名空间(默认开启,用shareProcessNamespace: true开启)。这样一来,sidecar 容器里的jps、ps能看到主容器的进程,Arthas 也能 attach 到主容器的 JVM 上。

这种模式的好处是:诊断工具和业务进程解耦,镜像可以独立构建,调试工具的升级也不影响业务。适合在生产环境长期保留一套标准化的诊断入口。但 sidecar 会增加 Pod 的资源占用,而且意味着修改 Deployment 配置,需要走变更流程。我在团队内部推广过一次,效果不错,但一定要记得给 sidecar 设置资源 limit,不然它疯起来会殃及主容器。

5.3 容器镜像缺失关键依赖时的临时办法

临时救急的场景,往往是不能进容器也不能改配置,只能在宿主机上想办法。可以先检查目标 Java 进程启动时的完整命令行,如果进程在宿主机上可见,并且/proc/<pid>/cmdline内容完整,那至少可以用系统层面的工具观察它。比如pidstat看 CPU、内存上下文切换,ss -tlnp看端口连接,这些不依赖 JVM attach,能拿到不少信息。

如果必须拿到 JVM 内部信息,可以尝试提前在 CI/CD 构建阶段把 JDK 的诊断工具打入镜像,或者用 Arthas 官方提供的 Docker 镜像作为调试镜像,通过docker run共享 PID namespace 后进入。总之,临时方案都挺绕,不如从一开始就为镜像预留诊断能力,省得以后再遇到同样问题。

6. 进阶排查:从 attach 机制入手定位问题

6.1 理解 attach 协议:文件 + 信号

如果你连续排查了好几次都搞不定,那就得从底层理解 JVM attach 协议的实现。这个过程大致是:attach 客户端先读取目标 JVM 在/tmp/hsperfdata_<user>/<pid>目录下生成的文件,获得目标 JVM 的一些信息,然后写一个.java_pid<pid>文件到目标工作目录,再给目标进程发送 SIGQUIT 信号。JVM 收到信号后,会启动 attach listener 线程,和客户端建立 socket 连接,之后就能进行 agent 加载、命令执行等操作。

从这套机制就能解释很多诡异问题。比如jps看不到进程,就是因为/tmp/hsperfdata_目录下的文件缺失或不可读;attach 失败,很多时候是写.java_pid文件没有权限,或者/proc不可访问;容器内 attach 不准,则是 PID 空间隔离导致 socket 地址不一致。

理解了底层机制,你就能顺着链路逐步排查:先检查/tmp目录,再检查/proc挂载,再检查信号是否被屏蔽,再检查安全策略。比对着报错信息漫无目的地试要高效得多。

6.2 使用 jcmd 和 jattach 做二分定位

遇到 attach 失败时,我习惯用二分法快速定位。先用jcmd <pid> VM.version验证 attach 链路,如果能通过,基本说明 JDK 本身没问题,问题在 Arthas 层,比如版本或参数。如果 jcmd 失败,问题基本在下层,比如权限、文件系统或 JVM 启动参数。

如果想进一步确认,可以尝试使用jattach,它是一个纯 native 的 attach 工具,不依赖 JDK 的 tools.jar,反馈更底层。如果jattach能成功,说明目标和系统环境支持 attach,剩下就查 ArThas 自身的兼容性。如果jattach也失败,基本可以断定是系统或 JVM 设置问题。

在某些极端情况下,可以用strace跟踪 attach 过程的系统调用。比如:

strace -f -e trace=process,network java -jar arthas-boot.jar -p 12345

观察日志里open、connect、kill等调用在哪一步报错,能精确到是权限、文件缺失还是 socket 连接失败。不过strace在容器里通常需要SYS_PTRACE权限,没有权限时可以尝试jcmd自带的-J-XX:+TraceClassLoading等参数,不过效果有限。

6.3 用 Arthas 自带的诊断选项和日志

Arthas 启动脚本本身提供了一些调试选项,不要忽略。我最常用的是-v,它会输出更详细的启动过程,包括扫描到的进程列表、attach 的日志、agent 加载情况等。如果启动失败,这些日志能直接指向问题。

日志文件一般在/tmp/arthas.log或者~/logs/arthas.log,里面有启动时的完整堆栈。我帮别人排查问题,第一件事就是让他把这段日志发给我,比描述“报错了”有用得多。

Arthas 还支持一些环境变量,比如ARTHAS_LOG_PATH可以改日志路径,ARTHAS_OPTS可以传 JVM 参数给 Arthas 自身。在高版本 JDK 下遇到模块访问问题时,可以试试设置:

export ARTHAS_OPTS="--add-exports=java.base/jdk.internal.vm=ALL-UNNAMED --add-exports=java.base/jdk.internal.misc=ALL-UNNAMED"

再启动 Arthas。这个技巧能解决很多 JDK 17+ 下的启动问题。

7. 避坑清单与实操总结

7.1 常见问题速查表

我把这几年遇到的 Arthas 启动时无法获取 java 进程的典型情况和解决方案整理成一张表,建议先收藏再看。

现象可能原因快速排查/解决
Can not find java process. Try to pass <pid> directly.自动扫描失败,进程不存在或不在同一环境用ps -ef | grep java确认进程,指定-p启动
jps -l为空但进程存在/tmp/hsperfdata_目录权限或用户不一致用同用户运行,或sudo执行,或进入容器执行
connect to target vm failedattach 被禁用/权限不足/attach 库缺失用jcmd <pid> VM.version验证,检查-XX:+DisableAttachMechanism
端口 3658/8563 无法访问端口冲突或防火墙限制ss -lntp查看占用,指定--telnet-port换端口
容器内 attach 失败PID 命名空间隔离/容器缺工具docker exec/kubectl exec进入容器运行,或用 sidecar
JDK 17+ 启动报模块错误模块系统访问限制升级 Arthas,或设置ARTHAS_OPTS加--add-opens/--add-exports
attach 成功但客户端 connect 不了网络命名空间/防火墙/端口映射问题telnet 127.0.0.1 3658测试,检查网络策略

这张表基本覆盖了绝大多数“无法获取 java 进程”的现场情景。遇到问题先按表格做一次对照,再决定下一步操作。

7.2 我的几条实际操作心得

最后聊几句个人经验,可能对你在生产环境处理问题有帮助。

第一,上线前先做一次 Arthas 启动演练。很多团队只在故障时才想起 Arthas,等到真要用却发现环境根本连不上。我建议在发布流程里加一个健康检查脚本,判断jcmd能否 attach 到自己的服务,能在早期就暴露权限或 JDK 配置问题。

第二,不要用 root 到处跑 Arthas。虽然 root 能解决权限问题,但会让权限相关的问题被掩盖,而且生产环境用 root 跑诊断工具,安全审计里多少有点说不过去。用服务账号,并且把账号和端口规范写进团队文档。

第三,学会用 Arthas 之外的诊断工具做交叉验证。jstack、jmap、jcmd都是 JDK 自带的,Arthas 连不上时它们可以作为备用。反过来,如果这些工具连不上,说明问题在 JVM 本身,优先检查进程状态和启动参数。

第四,把 Arthas 的日志积累起来。每次启动失败,都保留当时的arthas.log、ps -ef输出、jps -l输出,按日期归档。很多看着玄学的问题,翻几次日志就能发现规律。我记得有次连续几天在固定时间点报“无法获取进程”,后来发现是那个时刻有个定时任务会重启应用,而 Arthas 的启动时机刚好撞上重启窗口。这种问题不结合时间线,光看报错根本找不到答案。

Arthas 启动失败这件事,拆开看并不复杂,核心就是进程发现和 attach 连接两步。理解了底层机制,再配合一套有序的排查方法,基本上都能在几分钟内定位到原因。下次再遇到“Arthas 启动时无法获取 java 进程”,别急着重启服务,也别一上来就改启动参数,先按本文的顺序,从确认进程开始,逐步排除权限、容器、版本、端口这几个高发点。大多数时候,问题都会在几步之内露出真面目。

返回列表