JVM调优命令行工具实战:jps、jstat、jstack、jmap、jcmd与jinfo详解

发布时间:2026/10/13 9:29:52
JVM调优命令行工具实战:jps、jstat、jstack、jmap、jcmd与jinfo详解 1. 为什么命令行工具在JVM调优中依然不可替代很多人第一次接触JVM调优都是从一堆图形化工具开始的。界面友好、指标直观、点点鼠标就能看到堆内存曲线确实很省事。但真正到了线上环境尤其是容器化部署之后图形界面往往第一个被排除在外。原因很直接生产服务器通常不装桌面环境容器镜像追求最小化安全策略也不允许随意开放远程调试端口。这时候能救命的只有JDK自带的那几个命令行工具。我经历过一次典型的场景某个服务在凌晨突然响应变慢监控面板上只看到CPU使用率飙升但具体是哪个线程、哪段代码、哪类对象在捣鬼面板给不出答案。当时能用的只有一台跳板机和目标机器的SSH权限。最后靠jstack抓线程快照、jstat看GC频率、jmap导出堆转储三步定位到一个死循环的定时任务。整个过程没有图形界面参与全部在终端里完成。这就是命令行工具的价值它们不依赖额外环境随JDK一起分发在最小化容器里也能跑而且输出的是最原始、最可信的数据。图形工具本质上也是对这些命令的封装理解了底层命令你才知道界面上那些数字到底是怎么来的。这篇文章面向的是已经写过Java、但对JVM内部机制还比较模糊的开发者以及需要在生产环境做问题排查的运维和SRE。我会把常用的几个命令拆开讲清楚它们各自解决什么问题、参数怎么选、输出怎么读、有哪些坑。所有内容都基于实际排查经验不是API文档的复述。需要提前说明的是不同JDK发行版和版本之间工具的行为有差异。本文以主流的JDK 8和JDK 11为基准涉及差异的地方会单独标注。另外像jmap、jstack这类工具在较新版本中部分功能已被标记为废弃或需要额外参数开启这些变化我会在对应章节说明。2. jps与jstat先找到进程再看清运行时状态2.1 jps不只是ps的替代品jpsJVM Process Status Tool看起来很简单就是列出当前机器上所有Java进程。但它的价值在于只列出JVM进程并且能显示主类和传入参数。用ps -ef | grep java也能达到类似效果但jps的输出更干净而且能直接拿到lvmid本地虚拟机标识符这个ID是后续jstat、jstack等命令的入口参数。基本用法jps -l-l参数输出完整的主类全限定名或jar路径。如果只写jps输出的是主类简称遇到同名类容易混淆。还有一个常用参数-v可以显示传给JVM的参数比如堆大小、GC选择、系统属性等。排查问题时先确认目标进程启动时到底带了哪些参数能避免很多误判。jps -lv这里有个实际经验容器环境下jps可能看不到宿主机上其他容器的Java进程这是正常的因为PID命名空间隔离。如果你在容器里执行jps发现列表为空先确认容器内确实有Java进程在跑而不是工具坏了。另一个坑是jps依赖/tmp/hsperfdata_user目录下的性能数据文件。如果这个目录被清理、权限不对或者JVM启动时加了-XX:-UsePerfDatajps就看不到对应进程。这时候只能退回ps命令。所以我的习惯是jps能用就用用不了别纠结直接ps加grep。2.2 jstatGC频率和堆变化的实时窗口jstatJVM Statistics Monitoring Tool是我用得最多的工具没有之一。它能在不暂停应用的前提下持续输出GC、类加载、编译等运行时数据。对于判断“GC是否正常”这个问题jstat给出的答案比任何图形面板都直接。最常用的命令形式jstat -gcutil pid 1000 10含义是每1000毫秒采样一次共采样10次输出GC利用率统计。输出列包括各代内存区的使用百分比、YGC次数、YGCT年轻代GC总耗时、FGC次数、FGCTFull GC总耗时、GCTGC总耗时。读这个输出我关注三个点第一FGC是否持续增长。如果每次采样FGC都在涨说明Full GC频繁这是最危险的信号。正常服务可能几小时甚至几天才有一次Full GC如果几分钟一次必须立刻排查。第二老年代使用率OU的走势。如果OU在每次Full GC后回落很少或者根本不回落说明有对象持续晋升到老年代且无法回收大概率是内存泄漏。第三YGCT和FGCT的绝对值。如果GCT占总运行时间的比例超过5%说明GC已经影响到吞吐量需要考虑调整堆参数或优化对象分配。除了-gcutil还有几个有用的选项-gc输出各代容量和已用量单位是KB比百分比更精确。-gccapacity输出各代的最小、最大、当前容量用来判断堆是否已经扩展到上限。-class输出类加载和卸载数量排查类加载器泄漏时有用。-compiler输出JIT编译统计判断编译线程是否成为瓶颈。这里要提醒一个常见误区jstat的输出是采样值不是精确值。它读取的是性能数据文件里的计数器两次采样之间的数据可能被覆盖。所以不要用jstat去算精确的GC耗时它的作用是看趋势和频率不是做精确计量。还有一个参数选择问题采样间隔设多少我的经验是排查GC频繁问题时用1000毫秒观察长期趋势用5000毫秒或更长。如果间隔太短输出刷屏反而看不清间隔太长可能错过短暂的GC风暴。一般先跑jstat -gcutil pid 1000不加次数限制观察几十行后再决定是否调整。3. jstack与jmap线程卡顿和内存泄漏的现场取证3.1 jstack抓线程快照的正确姿势jstackStack Trace for Java用于生成JVM当前时刻的线程快照输出每个线程的调用栈。它的典型应用场景是应用响应慢、CPU高、死锁、线程阻塞。当监控告诉你“服务卡住了”jstack就是第一现场取证工具。基本命令jstack pid thread_dump.txt把输出重定向到文件方便后续分析。如果一次抓取不够可以间隔几秒连续抓三次对比线程状态变化。单次快照只能看到瞬间状态连续快照才能判断线程是在持续运行还是卡在同一个地方。输出内容里每个线程块包含线程名、优先级、线程IDnid、状态和调用栈。重点看几个状态RUNNABLE正在运行或等待CPU。如果大量线程处于RUNNABLE且调用栈相同可能是热点方法。BLOCKED等待监视器锁。如果多个线程BLOCKED在同一个锁上说明有锁竞争。WAITING或TIMED_WAITING等待条件或超时。正常线程池空闲线程通常处于这个状态不用紧张。排查CPU高的问题时我通常配合top -Hp pid找到占用CPU最高的线程ID然后把十进制转成十六进制在jstack输出里搜索nid0x...就能定位到具体是哪段代码在消耗CPU。这个转换用printf %x\n tid就能完成。top -Hp pid printf %x\n tid jstack pid | grep -A 30 nid0xhex_tid死锁检测是jstack的另一个强项。如果JVM检测到死锁输出末尾会直接打印Found one Java-level deadlock并列出涉及的线程和锁。但要注意jstack只能检测通过synchronized和ReentrantLock等Java层面产生的死锁涉及本地方法或外部资源的死锁它无能为力。一个实际踩过的坑某次线上服务卡死jstack输出里大量线程处于BLOCKED但死锁检测没有报告。后来发现是某个线程持有锁后调用了外部HTTP接口接口超时导致锁长时间不释放。这种“逻辑死锁”jstack不会主动识别需要人工分析调用栈。所以不要完全依赖自动死锁检测要养成逐块阅读线程栈的习惯。3.2 jmap导出堆转储与直方图jmapMemory Map for Java有两个核心用途生成堆转储文件heap dump和查看堆内对象统计。前者用于离线分析内存泄漏后者用于快速定位哪类对象占用了大量内存。查看对象直方图jmap -histo pid | head -30输出按实例数量和占用字节数排序显示类名、实例数、字节数。这个命令执行时会触发一次Full GC在JDK 8中默认行为所以对生产环境有短暂影响。如果不想触发GC可以加-histo:live但它同样会触发GC只是只统计存活对象。真正无影响的方式是使用jcmd的GC.class_histogram这个后面会讲。导出堆转储jmap -dump:formatb,fileheap.hprof pid这个操作会暂停应用Stop-The-World暂停时间取决于堆大小和磁盘速度。一个4GB的堆导出到本地磁盘可能需要几秒到十几秒。在生产环境执行前务必确认堆大小和磁盘剩余空间并尽量在低峰期操作。如果堆特别大可以考虑先用-histo缩小范围再决定是否导出完整dump。导出的hprof文件需要用专门工具分析比如Eclipse MAT或VisualVM。命令行下可以用jhat已废弃或jcmd的GC.heap_dump配合其他工具。我的习惯是线上只负责导出分析放到本地或专门的跳板机上做避免在故障机器上再增加负载。还有一个细节jmap -dump默认只导出存活对象相当于-dump:live但JDK 8的某些版本行为不一致。如果需要包含所有对象包括待回收的要显式指定-dump:all。这个差异在排查“对象为什么没被回收”时很关键。4. jcmd与jinfo新一代工具链和参数查看4.1 jcmd一个命令替代多个工具从JDK 7开始引入的jcmdJVM Command是一个多功能工具设计目标是逐步替代jmap、jstack、jinfo等分散的命令。它的用法是jcmd pid command不带参数执行jcmd会列出当前所有JVM进程jcmd pid help会列出该进程支持的所有命令。几个高频使用的子命令jcmd pid VM.flags jcmd pid GC.class_histogram jcmd pid GC.heap_dump /path/to/heap.hprof jcmd pid Thread.print jcmd pid VM.system_propertiesVM.flags输出JVM启动时的所有参数比jps -v更完整。GC.class_histogram等价于jmap -histo但不会强制触发Full GC取决于版本和参数对生产更友好。Thread.print等价于jstack。GC.heap_dump等价于jmap -dump。我现在的习惯是能用jcmd就用jcmd因为它的行为更一致而且在新版本JDK中持续维护。jmap和jstack在JDK 9之后部分功能被移除或需要-XX:UnlockDiagnosticVMOptions才能使用而jcmd的对应命令一直可用。一个实际对比在JDK 11上执行jmap -histo可能会提示“This command is not supported”但jcmd pid GC.class_histogram正常工作。所以如果你的环境是新版JDK优先记住jcmd的用法。4.2 jinfo查看和动态调整JVM参数jinfoConfiguration Info for Java用于查看JVM的系统属性和启动参数部分参数还支持运行时修改。查看所有系统属性jinfo -sysprops pid查看特定参数jinfo -flag MaxHeapSize pid jinfo -flag UseG1GC pid动态修改参数仅限标记为manageable的参数jinfo -flag HeapDumpOnOutOfMemoryError pid jinfo -flag -PrintGCDetails pid这里要强调能动态修改的参数非常有限主要是布尔类型的诊断开关比如HeapDumpOnOutOfMemoryError、PrintGC等。堆大小、GC选择这类核心参数无法在运行时修改。所以jinfo的主要价值是查看不是修改。一个实用技巧当你不确定某个参数是否生效时用jinfo -flag 参数名 pid查一下。比如怀疑G1没启用执行jinfo -flag UseG1GC pid输出-XX:UseG1GC说明启用了输出-XX:-UseG1GC说明没启用。这比翻启动脚本可靠得多。5. 把命令串起来一次完整的线上问题排查链路5.1 从告警到定位的完整流程前面分别讲了各个命令的用法但实际排查时它们是串在一起用的。我复盘一次真实的排查过程展示命令之间的衔接逻辑。告警内容某服务接口平均响应时间从50ms涨到800ms持续10分钟CPU使用率从30%涨到85%。第一步登录目标机器确认进程jps -lv | grep 服务名拿到PID后先看GC情况jstat -gcutil pid 1000 20输出显示FGC在20秒内从12次涨到18次FGCT从3.2秒涨到5.1秒。老年代使用率OU在每次Full GC后只从92%降到88%几乎不释放。初步判断老年代有大量对象无法回收Full GC频繁导致STW时间累积响应变慢。第二步看对象直方图确认是什么对象占着老年代jcmd pid GC.class_histogram | head -20输出显示某个自定义的缓存对象实例数达到200万占用约1.2GB。正常情况这个缓存应该只有几万条。怀疑缓存没有淘汰机制或者淘汰逻辑失效。第三步抓线程快照看是否有线程在持续往缓存里写jstack pid /tmp/td.txt grep -A 20 CacheLoader /tmp/td.txt发现一个定时任务线程处于RUNNABLE调用栈显示它在循环从数据库加载数据并放入缓存且没有退出条件。结合代码确认这个任务在某个异常分支下会无限循环。第四步导出堆转储做最终确认在低峰期执行jcmd pid GC.heap_dump /tmp/heap.hprof用MAT分析后确认缓存对象的引用链指向那个定时任务。问题定位完成。整个过程中jstat负责发现异常jcmd负责定位对象jstack负责找到代码位置jmap/jcmd负责最终取证。每个命令解决一个层次的问题不要指望一个命令给出所有答案。5.2 命令组合的常见误区和注意事项在实际使用中有几个反复踩到的坑值得单独说。第一不要在故障机器上做重操作。jmap -dump和jstack都会暂停应用如果机器已经CPU满载再执行这些命令可能直接导致服务不可用。我的原则是先做轻量采样jstat、jcmd GC.class_histogram确认方向后再决定是否做重量操作。如果必须导出堆转储尽量在流量低谷并提前确认磁盘空间。第二注意JDK版本差异。JDK 8和JDK 11在工具行为上有不少区别。比如jmap -histo在JDK 8默认触发Full GC在JDK 11可能不触发jstack在JDK 11对某些线程状态的输出格式有调整。排查前先确认版本java -version和jcmd pid VM.version都能看到。第三容器环境的特殊性。容器里看到的PID和宿主机不同jps、jstat等命令需要在容器内执行。如果容器镜像基于JRE而非JDK这些工具可能不存在。所以生产镜像建议基于JDK构建或者单独准备一个包含JDK工具的诊断镜像。另外容器内存限制和JVM堆参数不匹配是常见问题用jcmd pid VM.flags确认堆最大值是否超过了容器限制。第四采样数据的局限性。jstat是采样jstack是瞬间快照jmap -histo是统计。它们都不能还原完整的历史过程。如果问题已经过去这些命令帮不上忙需要依赖提前开启的GC日志或JFRJava Flight Recorder。所以不要等到出问题才想起这些工具平时就要在启动参数里加上GC日志比如-Xloggc:/path/to/gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps。这样出问题时日志和现场命令可以互相印证。6. 几个容易被忽略但关键时刻有用的命令6.1 jstat的隐藏用法和GC日志的配合jstat除了-gcutil还有一个-gcnew和-gcold分别看年轻代和老年代的详细情况。当-gcutil显示YGC频繁但FGC正常时用-gcnew看Eden区和Survivor区的变化能判断是对象分配速率过高还是Survivor区太小导致对象过早晋升。jstat -gcnew pid 1000 10 jstat -gcold pid 1000 10-gcnew输出里的TTTenuring Threshold和MTTMaximum Tenuring Threshold值得关注。如果TT很小比如1或2说明对象在年轻代只经历一两次GC就晋升到老年代这会加速老年代填充。原因可能是Survivor区太小或者-XX:MaxTenuringThreshold设置过低。jstat还有一个-printcompilation选项输出JIT编译的方法。如果发现某个热点方法反复编译可能是编译阈值设置不合理或者方法太大导致编译失败。jstat -printcompilation pid 1000这个输出在排查“为什么某个方法性能突然下降”时有用因为JIT去优化deoptimization会导致方法回退到解释执行性能骤降。6.2 jcmd的VM.native_memory和JFRjcmd还支持两个高级功能VM.native_memory和JFR。VM.native_memory用于查看JVM本地内存使用情况需要启动时加-XX:NativeMemoryTrackingsummary或detail。输出包括Java堆、元空间、线程栈、代码缓存、GC等各部分的本地内存占用。排查“堆内存正常但进程RSS很高”的问题时这个命令是唯一的选择。jcmd pid VM.native_memory summaryJFRJava Flight Recorder是JDK 11之后开源的飞行记录器可以低开销地记录JVM运行时的各种事件。用jcmd启动和停止记录jcmd pid JFR.start namemyrec duration60s filename/tmp/rec.jfr jcmd pid JFR.dump namemyrec filename/tmp/rec.jfr jcmd pid JFR.stop namemyrec生成的jfr文件可以用JDK Mission Control分析能看到GC、线程、锁、IO等详细事件。JFR的开销极低适合在生产环境长期开启作为事后分析的“黑匣子”。6.3 命令行工具的替代方案和选择建议虽然这篇文章讲的是命令行工具但有必要提一下替代方案方便你根据场景选择。如果环境允许Arthas是一个很好的补充。它提供了dashboard、thread、heapdump、jad等命令交互体验比原生工具好而且支持在线反编译和热更新。但Arthas需要额外下载和启动在极端受限的环境里可能用不了。VisualVM和JConsole适合本地开发和测试环境可以直观地看内存曲线和线程状态。但生产环境通常不开放JMX端口所以它们更多用于复现问题后的分析。Eclipse MAT是分析堆转储的利器它的支配树和泄漏 suspects 报告能快速定位内存泄漏根源。但MAT需要足够的本地内存来加载hprof文件大堆转储可能需要几十GB内存。我的建议是命令行工具是基本功必须熟练图形工具是效率提升按需使用。在面试和实际排查中能说清楚jstat输出里每个列的含义比会用十个图形工具更有说服力。最后分享一个个人习惯我会在每台生产服务器的/opt目录下放一个jvm-diag.sh脚本内容就是几个常用命令的组合比如自动抓取jstat、jstack、jcmd GC.class_histogram并打包。出问题时一键执行减少手忙脚乱。脚本里会加上时间戳和PID方便归档对比。这个习惯帮我节省了大量重复操作的时间也避免了紧张时敲错命令。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询