1. 被忽视的JVM入口工具jps到底在什么场景下不可替代很多人第一次接触Java进程排查都是从ps -ef | grep java开始的。这条命令确实能列出所有Java进程但它有个致命问题输出信息太杂你只能看到一长串启动参数却很难快速定位到“这个进程对应的是哪个主类”“它的JVM参数是什么”“它是不是我要找的那个应用”。jpsJava Virtual Machine Process Status Tool就是为解决这个痛点而生的——它是JDK自带的一个轻量级命令行工具专门用来列出当前系统中正在运行的Java进程及其主类名、JVM参数和进程ID。我第一次真正意识到jps的价值是在一台跑了七八个Java服务的测试机上。当时需要确认某个定时任务服务是否还活着用ps命令翻了三屏才找到对应的进程而同事直接敲了一个jps -l一秒钟就定位到了目标。从那以后jps就成了我排查Java进程问题的第一入口。这篇文章适合所有需要和Java进程打交道的开发者、运维人员和测试人员。不管你是刚接触Linux命令行的新手还是已经能熟练使用jstack、jmap的老手jps都能帮你省下大量“找进程”的时间。我会从它的工作原理讲起把常用参数、输出解读、典型使用场景、踩坑经验全部拆开揉碎让你看完就能直接上手用。提示jps位于$JAVA_HOME/bin目录下只要JDK安装正确并且环境变量配置无误任何路径下都可以直接调用。如果你用的是JRE而非JDK这个命令是不存在的。2. jps的输出到底是怎么来的从attach机制说起2.1 它并不是简单地扫描进程列表很多人以为jps就是包装了一下ps命令其实完全不是。jps的工作机制和ps有本质区别。当你执行jps时它会做两件事第一扫描系统临时目录下由JVM创建的hsperfdata_*目录第二通过JVM的attach机制与目标进程通信获取更详细的信息。具体来说每个Java进程启动后会在系统的临时目录Linux下通常是/tmpWindows下是%TEMP%下创建一个名为hsperfdata_用户名的目录里面存放一个以进程PID命名的文件。这个文件记录了该JVM的性能数据和基本信息。jps首先扫描这个目录列出所有能找到的PID然后再尝试attach到这些进程上获取主类名和JVM参数。这就解释了一个常见现象为什么有时候jps列出的进程信息不完整主类名显示为--或者Unknown。原因就是attach失败了——可能是权限不够可能是目标JVM没有开启attach能力也可能是临时目录被清理了。2.2 hsperfdata目录的关键作用hsperfdata目录是jps能工作的基础。你可以手动去看一下这个目录ls -la /tmp/hsperfdata_$(whoami)/你会看到一堆以数字命名的文件每个数字就是一个Java进程的PID。如果这个目录不存在或者里面是空的jps就什么都列不出来。这里有个容易踩的坑如果你用sudo切换到root用户执行jps而Java进程是以普通用户身份启动的那么root用户看到的是/tmp/hsperfdata_root目录里面是空的自然就找不到那些进程。反过来也一样。所以执行jps的用户必须和启动Java进程的用户一致否则你看到的列表可能是不完整的。2.3 attach机制与权限的关系jps获取主类名和JVM参数依赖的是JVM的attach机制。这个机制允许一个JVM进程连接到另一个JVM进程上执行一些管理操作。attach成功的前提是两个进程的启动用户相同或者执行方有足够的权限目标JVM没有禁用attach功能临时目录的权限设置允许访问如果attach失败jps仍然会列出PID但主类名会显示为--。这时候你只能看到进程存在但不知道它具体跑的是什么。注意在某些安全加固的环境中hsperfdata目录的权限被收紧或者临时目录被设置了noexec挂载选项都会导致jps无法正常工作。遇到这种情况不要怀疑命令本身有问题先检查目录权限和挂载配置。3. 常用参数逐个拆解每个选项背后解决什么问题3.1 不加参数最简输出直接执行jps输出格式是“PID 主类名”$ jps 12345 Jps 12346 MyApplication 12347 Launcher注意第一行通常是jps自己的进程因为jps本身也是一个Java程序。这个自我列出的行为有时候会让人困惑但它是正常的。不加参数时jps只显示主类名不含包路径对于简单的应用够用但如果多个应用的主类名相同就分不清了。3.2 -l显示完整包名-l参数的作用是输出主类的完整包名或者JAR文件的完整路径$ jps -l 12346 com.example.order.OrderService 12347 /opt/apps/payment-service.jar这个参数在实际排查中非常实用。当你有多个Spring Boot应用同时运行时它们的主类可能都是org.springframework.boot.loader.JarLauncher不加-l根本分不清谁是谁。加上-l之后JAR包的完整路径一目了然。3.3 -v查看JVM启动参数-v参数会输出传递给JVM的参数比如堆大小、GC策略、系统属性等$ jps -v 12346 OrderService -Xms512m -Xmx1024m -XX:UseG1GC -Dspring.profiles.activeprod这个输出对于排查“这个进程到底用了多大的堆”“GC配置是什么”这类问题特别有用。你不需要去翻启动脚本直接一条命令就能看到。但要注意-v输出的参数是JVM层面的参数不包括main方法的参数。而且输出可能很长如果启动参数特别多建议配合grep过滤。3.4 -m查看main方法参数-m参数输出传递给main方法的参数$ jps -m 12346 OrderService --server.port8080 --spring.config.location/etc/app/这个在排查“应用启动时到底传了什么参数”时很有用。比如你怀疑某个配置没生效先看看启动参数里有没有传对。3.5 -q只输出PID-q参数只输出PID不输出主类名$ jps -q 12346 12347这个参数看起来简单但在写脚本时特别有用。比如你想批量对某个应用的所有实例执行操作可以先用jps -q拿到PID列表再循环处理。3.6 -V只输出PID和主类名-V大写输出PID和主类名但不输出JVM参数。它和不加参数的区别在于-V会抑制jps自身进程的显示。这个细节在写自动化脚本时能省去过滤自身进程的麻烦。3.7 组合使用实际场景中的最佳搭配单独用一个参数往往不够实际工作中最常见的是组合使用# 查看完整包名和JVM参数 jps -lv # 查看完整包名和main方法参数 jps -lm # 查看所有信息 jps -lvm我个人的习惯是先用jps -l快速定位目标进程确认PID后再用jps -lv查看详细的JVM参数。这样分两步走信息不会一次性刷屏排查效率更高。参数输出内容典型使用场景无PID 主类名快速确认Java进程是否存在-lPID 完整包名/JAR路径区分多个同类应用-vPID 主类名 JVM参数排查堆大小、GC配置-mPID 主类名 main参数确认启动参数是否正确-q仅PID脚本中批量获取PID-VPID 主类名不含jps自身自动化脚本中避免过滤4. 真实排查场景jps在问题定位中的实际用法4.1 确认服务是否真的启动了这是jps最基础也最高频的用法。当你部署完一个Java应用不确定它有没有成功启动时第一件事就是jps -l看看进程在不在。但这里有个细节进程存在不代表服务可用。JVM启动了但应用可能还在初始化或者已经卡死了。jps只能告诉你“进程活着”不能告诉你“服务正常”。所以确认进程存在之后还需要配合端口检查或健康检查接口来验证服务状态。我遇到过好几次这样的情况jps能看到进程但应用实际上已经OOM卡住了日志也不再输出。这时候就需要进一步用jstack看线程状态或者用jstat看GC情况。jps是排查的起点不是终点。4.2 定位占用高CPU的Java进程当服务器CPU飙高时你需要快速找到是哪个Java进程在捣乱。通常的做法是# 第一步用top找到高CPU的PID top -c # 第二步用jps确认这个PID对应哪个Java应用 jps -l | grep PID或者反过来先用jps -l列出所有Java进程再逐个用top -p PID查看CPU占用。两种顺序都可以取决于你是“先看到高CPU再找应用”还是“先列出应用再逐个检查”。4.3 批量管理多个Java实例在一台机器上跑多个相同应用的实例时jps配合grep可以快速筛选出目标实例# 找出所有OrderService的进程 jps -l | grep OrderService # 统计实例数量 jps -l | grep -c OrderService如果需要批量停止这些实例可以这样操作for pid in $(jps -q -l | grep OrderService | awk {print $1}); do kill $pid done当然生产环境批量操作要谨慎建议先确认PID列表无误再执行。4.4 排查“进程莫名消失”的问题有时候Java进程会突然消失日志里也没有明显的错误。这时候可以用jps的历史记录来辅助判断。虽然jps本身不保存历史但你可以写一个简单的监控脚本定期执行jps -l并记录结果#!/bin/bash while true; do echo $(date) /var/log/jps_monitor.log jps -l /var/log/jps_monitor.log sleep 60 done这样当进程消失时你可以通过日志确认它最后一次出现的时间再结合系统日志如dmesg排查是不是被OOM Killer杀掉了。提示这个监控脚本只适合临时排查用长期运行建议用专业的监控工具。脚本本身也会消耗少量资源注意不要在生产环境长时间跑。5. jps不输出内容或输出不全排查思路与解决方案5.1 执行jps后只显示自身进程这是最常见的问题。执行jps后只看到一行Jps其他Java进程一个都没有。原因通常有以下几种原因一用户不一致。当前执行jps的用户和启动Java进程的用户不同。比如Java进程是appuser启动的你用root执行jps看到的就是空的。解决办法是切换到对应用户再执行或者用sudo -u appuser jps。原因二临时目录被清理。hsperfdata目录被系统清理工具删掉了。这种情况在容器环境中特别常见因为容器的/tmp目录可能被挂载为tmpfs重启后数据丢失。解决办法是重启Java进程让它重新创建hsperfdata文件。原因三JVM参数禁用了perfdata。如果启动时加了-XX:-UsePerfDataJVM就不会创建hsperfdata文件jps自然找不到。检查启动脚本中是否有这个参数。5.2 主类名显示为--jps能列出PID但主类名显示为--说明attach失败了。可能的原因包括目标JVM的attach功能被禁用-XX:DisableAttachMechanism临时目录权限不足无法读取目标进程的perfdata文件目标JVM版本与jps版本不兼容比如用JDK 8的jps去attach JDK 17的进程解决办法先确认权限再确认JVM参数最后确认版本一致性。如果是版本问题用对应版本的jps即可。5.3 在容器环境中jps找不到进程容器环境是jps问题的高发区。常见原因有容器的基础镜像只有JRE没有JDK根本没有jps命令容器的/tmp目录权限设置导致hsperfdata无法创建容器内使用了非root用户但/tmp目录属于root且没有写权限解决办法确认镜像中包含JDK检查/tmp目录权限必要时在启动脚本中显式设置-Djava.io.tmpdir到一个可写的目录。5.4 jps输出乱码或中文显示异常这个问题通常出现在Windows环境下原因是控制台编码和JVM默认编码不一致。解决办法是设置JAVA_TOOL_OPTIONS环境变量export JAVA_TOOL_OPTIONS-Dfile.encodingUTF-8或者在执行jps时指定编码jps -J-Dfile.encodingUTF-8Linux环境下这个问题比较少见但如果遇到同样可以用上面的方法解决。问题现象可能原因解决方案只显示Jps自身用户不一致切换到启动用户执行只显示Jps自身hsperfdata被清理重启Java进程主类名为--attach失败检查权限和JVM参数容器内无输出无JDK或tmp权限问题确认镜像和目录权限输出乱码编码不一致设置file.encoding6. jps与其他JVM工具的配合使用构建完整的排查链路6.1 jps jstack定位线程阻塞当你发现某个Java应用响应变慢时排查链路通常是# 第一步用jps找到目标进程PID jps -l | grep MyApp # 第二步用jstack导出线程栈 jstack PID /tmp/thread_dump.txt # 第三步分析线程栈查找BLOCKED或WAITING的线程 grep -A 10 BLOCKED /tmp/thread_dump.txtjps在这个链路中的作用是快速提供PID省去了手动查找的步骤。虽然jstack也可以直接用PID但前提是你得先知道PID。6.2 jps jstat监控GC情况排查内存问题时jstat是必不可少的工具# 找到PID jps -l | grep MyApp # 每1秒输出一次GC情况共输出10次 jstat -gcutil PID 1000 10jstat的输出包括Eden区、Survivor区、老年代的使用率以及GC次数和耗时。结合jps -v看到的堆大小配置可以判断是否存在内存分配不合理的问题。6.3 jps jmap分析堆内存当怀疑内存泄漏时可以用jmap导出堆快照# 找到PID jps -l | grep MyApp # 导出堆快照 jmap -dump:formatb,file/tmp/heap.hprof PID导出的堆快照可以用MAT等工具分析找出占用内存最多的对象。jps在这里同样只是提供PID的入口。6.4 jps jinfo查看和动态修改JVM参数jinfo可以查看运行中JVM的参数甚至动态修改部分参数# 查看所有系统属性 jinfo -sysprops PID # 查看特定参数 jinfo -flag MaxHeapSize PID # 动态开启GC日志部分参数支持动态修改 jinfo -flag PrintGCDetails PID这个组合在需要临时调整JVM参数但又不想重启应用时特别有用。6.5 工具链的协同逻辑这些工具的共同点是都需要PID作为输入而jps就是获取PID最便捷的方式。整个排查链路可以总结为jps -l找到目标进程jps -lv查看JVM参数确认配置jstat看GC和内存趋势jstack看线程状态jmap导出堆快照做深度分析jinfo查看或动态调整参数每一步都依赖前一步的PID而jps就是这条链路的起点。7. 几个容易踩的坑和实战经验7.1 jps不是万能的它只能看到“活着的”进程jps只能列出当前正在运行的Java进程。如果进程已经崩溃退出jps是看不到的。所以排查“进程为什么消失”这类问题时jps只能告诉你“现在没有”不能告诉你“为什么没有”。这时候需要结合系统日志、应用日志和监控数据来综合分析。7.2 生产环境慎用jps -v输出完整参数jps -v会输出完整的JVM参数其中可能包含敏感信息比如数据库密码、密钥路径等如果这些是通过-D参数传入的。在生产环境执行时注意输出内容不要泄露到不安全的渠道。建议在安全的终端中执行或者将输出重定向到受保护的日志文件。7.3 容器中jps的替代方案在容器环境中如果jps不可用可以用以下命令替代# 查看容器内的Java进程 ps -ef | grep java # 或者查看/proc目录下的Java进程 for pid in /proc/[0-9]*; do if grep -q java $pid/comm 2/dev/null; then echo $(basename $pid): $(cat $pid/comm) fi done这些替代方案虽然不如jps直观但在jps不可用时能起到类似的作用。7.4 jps的输出顺序是不确定的jps输出的进程顺序并不是按照PID排序的也不是按照启动时间排序的。如果你需要按特定顺序查看可以配合sort命令# 按PID排序 jps -l | sort -n # 按主类名排序 jps -l | sort -k27.5 定期清理hsperfdata目录如果发现/tmp/hsperfdata_*目录下堆积了大量文件说明有些Java进程异常退出后没有清理自己的perfdata文件。这些残留文件虽然不影响系统运行但会占用少量磁盘空间也可能导致jps输出中包含已经不存在的PID。可以定期清理# 清理超过7天未修改的perfdata文件 find /tmp/hsperfdata_* -type f -mtime 7 -delete但要注意正在运行的Java进程的perfdata文件不要删否则jps会找不到对应的进程。7.6 用jps写监控脚本的注意事项用jps写监控脚本时要注意过滤掉jps自身的进程。虽然jps自身进程通常很快退出但在高频执行的脚本中可能会造成误判。建议使用-V参数不显示自身进程或者在脚本中排除包含Jps的行jps -l | grep -v Jps | grep MyApp另外脚本中执行jps的用户要和Java进程的用户一致否则会漏掉目标进程。7.7 jps在JDK版本升级后的行为变化不同JDK版本的jps行为略有差异。比如JDK 8的jps和JDK 17的jps在输出格式上基本一致但在attach机制上有所变化。如果系统中同时安装了多个JDK版本建议使用与目标Java进程相同版本的jps避免兼容性问题。我在实际使用中的体会是jps虽然简单但它是JVM排查工具链中不可或缺的一环。它的价值不在于功能多强大而在于它足够轻量、足够快能在你需要的瞬间给出最关键的PID信息。掌握好这个工具再配合jstack、jstat、jmap等工具就能构建起一套完整的Java进程排查能力。最后再分享一个小技巧把常用的jps组合命令做成alias比如alias jpsljps -lv能省下不少敲键盘的时间。
阅读完成 · 觉得有帮助?