资讯动态

JDK自带工具实战:从jps到jcmd,Java线上问题排查指南

发布时间:2026/10/20 14:55:02 来源:尧图企业网站定制
做Java应用运维这些年我最怕的不是服务彻底宕机而是服务还活着却病恹恹的——CPU飙到90%以上、接口偶尔超时、内存缓慢上涨。翻业务日志只能看到一堆业务异常JVM内部到底发生了什么完全是一团黑。后来我养成了一个习惯先不急着看日志而是用JDK自带的命令行工具给JVM做一次“望闻问切”确认症状再下判断。这套方法帮我解决过无数次线上疑难杂症今天把最常用的几个命令和排查思路完整梳理一遍希望对做Java后端、SRE、DBA或者自己维护服务的同学有直接帮助。先说清楚适用范围这些工具都在JDK的bin目录下只要机器上装了JDK就能用不需要额外装agent、不需要改代码、也不需要停服务。和Arthas这类增强型工具相比它们更轻量、更“原生”生产环境受限较多时反而是最可靠的一批武器。我会按照“找进程—看内存—看GC—抓线程—导堆转储—改参数”这条主线来写最后给一个完整的实战案例串起来看完就能直接照着操作。1. 准备工作与环境确认1.1 先确认Java版本和进程归属排查之前第一件事不是急着敲命令而是先搞清楚几个基本情况这台机器上有几个Java进程每个进程用的是哪个JDK版本JAVA_HOME配没配如果机器上装了多个JDK比如JDK 8和JDK 11共存那么你调用的jstack、jmap版本可能和进程实际运行的版本不一致。版本不匹配轻则命令报错重则导出的堆转储打不开白白浪费一次宝贵的现场采集机会。我一般在接到告警后的第一步会执行which java java -version ps -ef | grep java先确认默认的java指向哪个版本再通过ps确认目标进程的启动命令里有没有显式指定JAVA_HOME。这里有个容易被忽略的点很多部署脚本会在启动命令里写死JAVA_HOME比如/usr/local/jdk1.8.0_202/bin/java -jar app.jar这种情况下你用java -version看到的版本未必就是进程实际用的版本最好直接执行进程对应的那个java路径来确认/usr/local/jdk1.8.0_202/bin/java -version确认版本后再看进程的身份。如果目标进程是以root或专用服务账号启动的而你当前用普通用户登录执行jstack、jmap时大概率会遇到Unable to open socket file或者Permission denied这类错误。这个后面专门说但提前记住一条排查线上问题最好用和启动进程相同的账号或者直接sudo切过去能省掉很多不必要的麻烦。1.2 JRE工具全家福哪些命令平时用得上JDK的bin目录下可执行文件不少但真正用于日常问题排查的就那么几个。我用一张表先帮大家建立整体印象后文逐个展开。工具核心作用典型场景jps列出Java进程及JVM参数定位目标进程PID多实例环境区分jcmd多功能命令行诊断打印线程、堆信息、GC统计、系统属性jstat查看类加载、GC、编译统计判断GC频率、内存增长趋势jinfo查看/修改JVM配置核对启动参数是否生效jstack打印线程快照定位高CPU线程、死锁、线程卡顿jmap堆内存统计与堆转储导出内存泄漏、OOM前兆分析jhsdb替代部分jmap/jstack底层能力JDK 9环境下排查崩溃问题这里补充一句很多人以为这些工具运行时会影响业务性能不敢在生产用。实际上像jstat、jinfo、jps这类只读命令开销极低完全可以随时执行。jstack和jmap属于相对“重”的操作因为需要JVM暂停到安全点来采集数据但在我实践下来对于大多数应用一次线程转储带来的停顿在几十到几百毫秒量级生产上只要不是极度敏感的链路基本可接受。真正需要小心的是对超大堆几十GB执行jmap -dump这个后面详细说。2. 定位进程jps与jcmd的基础用法2.1 jps找PID的第一选择排查任何Java问题第一步永远是拿到正确的PID。有人习惯用ps -ef | grep java当然可以但在同一台机器跑了几十个Java实例时ps的输出会非常嘈杂而且它只能看到操作系统层面的进程信息看不到JVM内部的属性。jps的定位就是“专门列出Java进程”输出干净、信息结构化用起来要顺手得多。jps -l -v-l显示进程主类的完整包名或jar包路径-v显示JVM的启动参数。这个组合我几乎每次必用因为一眼就能看到每个进程的启动参数比如堆大小、GC收集器、远程调试端口等。举个例子输出可能长这样12345 /data/app/order-service.jar -Xms2g -Xmx2g -XX:UseG1GC -Dspring.profiles.activeprod 12346 /data/app/pay-service.jar -Xms1g -Xmx1g -XX:UseParallelGC这样我不用去翻部署文档直接就知道order-service用的是G1、堆2GBpay-service用的是ParallelGC、堆1GB。平时看问题这个信息量已经足够做初步判断。需要注意的是jps是通过查找系统里的/tmp/hsperfdata_用户名目录来获取JVM进程信息的。如果某Java进程是以另一个用户身份启动的你的jps很可能看不到它。这时候用ps -ef | grep java辅助确认PID然后用sudo执行后续命令。2.2 jcmd更通用的进程交互入口jcmd从JDK 7开始就有了但很多人还在老老实实用jps、jstack、jmap分开操作。实际上jcmd是一个“超级工具”几乎所有排查命令都可以通过它发出而且对进程的识别方式更直接jcmd -l这个命令列出所有Java进程输出格式和jps类似但最大的好处是它支持按PID向指定进程发指令。比如查看线程快照jcmd 12345 Thread.print查看堆信息jcmd 12345 GC.heap_info查看系统属性jcmd 12345 VM.system_properties我后面会专门用一个章节讲jcmd这里先记住一个结论新版JDK环境下尤其是JDK 11以后jcmd能覆盖大部分jstack、jmap、jinfo的职责而且用法更统一。线上能用jcmd办的事我优先用jcmd。3. jstat观察JVM内存与GC健康状况3.1 五分钟看懂GC统计输出jstat是我诊断内存和GC问题的第一选择入侵小、信息密度高。最常用的命令是jstat -gcutil pid 间隔毫秒 次数比如jstat -gcutil 12345 1000 10每隔1秒输出一次共输出10次。-gcutil输出的是各内存区域的使用率百分比比-gc输出具体容量更直观。我解释一下典型输出里每列的含义列名含义警惕信号S0 / S1两个Survivor区使用率长期接近100%说明对象晋升异常EEden区使用率持续高位且GC后不降说明对象分配过快O老年代使用率持续增长且GC后不降是最危险的信号M元空间使用率持续增长可能存在类加载器泄漏CCS压缩类空间使用率一般随M波动YGC / YGCTYoung GC次数 / 累计耗时次数暴增说明短命对象太多FGC / FGCTFull GC次数 / 累计耗时次数频繁是性能杀手第一次用jstat的人容易犯一个错误只看一次输出就下结论。GC数据是动态的必须连续观察多轮比如间隔1秒看10轮才能看出趋势。如果每轮老年代占用都在涨、Eden区每次GC后马上又满了这就说明对象分配速率远高于回收速率内存压力已经很大了。3.2 判断到底该调堆还是调代码有次某团队找我帮忙看一个批量任务服务日志里频繁出现老年代分配失败Full GC每几分钟就来一次。我上去先执行jstat -gcutil 23456 2000 10观察结果很有代表性YGC次数在10轮里涨了几十次每次Young GC后Eden使用率从99%掉到10%左右但老年代使用率从62%一路爬到了78%完全没有下降趋势。这说明绝大部分对象在Young GC后没有死掉而是被晋升到了老年代最终老年代扛不住触发Full GC。这种情况下加堆只是饮鸩止渴因为对象的“存活率”太高堆再大也会被填满。真正该做的是检查代码里是不是把大对象、长生命周期对象放进了缓存或者是不是有连接、会话没有关闭导致对象一直被引用。我的习惯是如果老年代在Full GC后能明显下降说明对象是可以回收的可能是GC参数不合适如果Full GC后老年代几乎不降说明存在大量本该回收但被强引用的对象优先查代码而不是调参数。3.3 jstat观测实例再给一个生产环境真实的观测片段便于对照jstat -gcutil 12345 2000 5输出示例S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT 0.00 0.00 82.31 45.22 88.44 87.10 18234 23.451 3 1.202 4 0.088 0.00 0.00 85.12 45.30 88.44 87.10 18235 23.459 3 1.202 4 0.088 ...观察要点S0/S1基本是0.00说明Survivor区几乎没有对象留住绝大多数对象在Young GC时直接回收Eden到了85%左右会有一次YGC但40多秒内YGC次数从18234涨到18240频率很高但YGCT增量不大说明Young GC本身很快老年代稳定在45%左右没有上涨趋势FGC只发生过3次。这种情况可以放心GC整体健康服务没大问题。如果业务高峰期Eden涨得快、YGC频率高但每次都能及时回收也不需要过度紧张这是正常的对象分配节奏。4. jstack线程快照与高CPU问题定位4.1 抓一次线程快照的正确姿势jstack用于打印JVM当前的线程转储包括每个线程的状态、栈帧、锁信息。日常排查高CPU、接口卡顿、线程池耗尽都离不开它。基本用法jstack pid /tmp/thread_dump_$(date %Y%m%d_%H%M%S).txt建议加上时间戳重定向到文件而不是直接打印到屏幕——线上线程数动辄几百上千直接打印会刷屏而且你很难一边滚动一边分析。更推荐用jcmd统一操作jcmd pid Thread.print /tmp/thread_dump.txtjstack输出里每个线程都有一个nid字段这是操作系统层面的线程ID十六进制。高CPU问题定位的核心思路就是把这个nid和操作系统的线程ID对上号。标准流程分四步第一步用top命令找到CPU占用最高的Java进程拿到PIDtop -c第二步查看这个进程内哪个线程最耗CPUtop -Hp pid第三步把耗CPU最高的线程ID从十进制转成十六进制printf %x\n 线程ID第四步在jstack输出文件里搜这个十六进制nid就能看到对应的线程栈grep -A 20 nid0x十六进制ID /tmp/thread_dump.txt这套流程我执行过无数次定位高CPU线程基本在1分钟内搞定。4.2 一眼识别线程状态背后的含义jstack输出里每个线程都有状态标识常见的有RUNNABLE、BLOCKED、WAITING、TIMED_WAITING。很多人看到RUNNABLE就以为线程在正常干活其实不一定。RUNNABLE分两种一种是真正在CPU上执行计算一种是正在执行IO读写但还没返回线程状态同样是RUNNABLE。区分方法就是结合top -Hp看CPU占用——如果nid对应的线程CPU占用高且栈顶是HashMap.get、Regex匹配、序列化这类纯计算代码那基本就是算法或数据量问题如果是SocketInputStream.read这类IO方法说明线程在等待网络数据属于正常的IO阻塞。BLOCKED状态说明线程在等待一把锁。如果大量线程都BLOCKED在同一把锁上并且栈里指向同一个业务对象那就是典型的锁竞争问题。我在一个库存服务里见过这样的场景一个同步方法内部做了远程调用锁持有时间被拉长到几十毫秒高峰期上千个线程排队等锁接口RT从50ms飙升到5秒。jstack抓下来一半以上的业务线程都卡在同一行synchronized代码上问题定位非常直观。TIMED_WAITING和WAITING要分场景看。如果栈顶是LockSupport.parkNanos、Object.wait并且对应的是线程池的空闲线程那是正常现象如果业务线程大量WAITING在某个条件上且长时间不恢复可能是线程池队列满了、信号量被占满、或者依赖的下游服务拖死了线程。4.3 死锁检测的实用套路死锁问题在复杂的多线程业务里并不罕见。jstack有一个参数-l会在线程转储末尾额外输出锁的详细信息并且如果JVM检测到了死锁会明确打印Found one Java-level deadlock同时列出死锁的线程和它们持有的锁。jstack -l pid /tmp/thread_dump_lock.txt这里有个小技巧死锁检测依赖JVM的锁监控信息如果加了-l也没看到明显死锁提示别急着下结论“没有死锁”。有些隐蔽的死锁是循环等待但还处于可超时的状态比如数据库连接池获取连接等待超时、分布式锁等待超时这类问题jstack只能看到“等待”看不到“死锁”需要结合业务日志和超时时间一起判断。4.4 jstack抓取的关键注意事项jstack不是任何时候都能成功抓取。遇到以下情况要有预案一是线程数特别多的大应用抓取时间长且文件大分析时要先grep关键线程而不是全量阅读。二是进程处于极度繁忙或卡死状态时jstack可能等待很久甚至超时这时候可以试试jstack -F pid强制模式但-F模式打印的信息量会少一些优先还是正常抓取。三是JVM刚启动或者正在执行GC时抓取线程栈可能不完整最好等GC结束再抓。四是每次抓取之间最好间隔一段时间连续抓多份比如5秒抓一份、连抓3份对比线程状态的变化比单份快照更能说明问题。我自己的习惯是接到CPU告警后先top -Hp锁定热点线程再连续抓3份jstack每份间隔3到5秒。对比后发现某个线程一直RUNNABLE且栈不变说明它在死循环或长时间执行重计算如果每次栈都在变那可能只是正常的业务波动虚惊一场。5. jmap堆内存统计与堆转储导出5.1 快速查看堆内存概况jmap主要用于查看堆内存使用情况和导出堆转储。常用命令jmap -heap pid这条命令输出当前堆的配置、各代内存的容量和使用率。它的作用和jstat -gcutil有些重叠但它展示的是静态配置信息适合确认参数有没有生效。比如你想确认生产环境到底用的什么GC收集器、新生代比例是不是默认值一条jmap -heap看得很清楚。我更常用的是查看堆内对象分布命令是jmap -histo:live pid | head -50这个命令输出堆中存活对象的类名、实例数量和占用空间按大小降序排列。-histo:live会先触发一次Full GC统计的是Full GC之后的存活对象这种方式可以过滤掉大量应该回收的临时对象让“真正的占用大户”暴露出来。在某次排查OOM前兆时我发现一个业务的ConcurrentHashMap对象占了堆总内存的60%顺着这个对象找到代码里的全局缓存Map果然是无脑往缓存里塞数据且完全没有清理机制。这里要特别提醒-histo:live会触发Full GC在流量高峰期执行可能会引发较长时间的停顿。线上操作要谨慎如果只是想看整体占比用不带live的jmap -histo即可虽然包含垃圾对象但同样能看到大致格局权衡下来我更推荐先跑不带live的等低峰期再确认存活对象情况。5.2 导出堆转储的正确方式当怀疑内存泄漏或者需要深入分析对象引用链时必须导出完整的堆转储文件然后丢到MAT、VisualVM这类工具里仔细查。命令jmap -dump:live,formatb,file/data/dump/heap_$(date %Y%m%d_%H%M%S).hprof pid这里的live同样会触发Full GC确保dump出来的是存活对象。有一个非常关键的实操点先确认磁盘空间够不够。堆转储文件的大小通常接近堆的使用量比如堆上限4GB、用了3.5GBdump文件可能就是3GB多。如果/tmp或者指定目录空间不足dump会直接失败。我习惯在导出前用df -h确认目标目录剩余空间至少是堆已用大小的1.5倍。另一个关键点是导出对业务的冲击。对超大堆执行dumpJVM需要把所有线程暂停到安全点然后序列化堆对象这个过程可能长达十几秒甚至几分钟对线上高并发服务来说是致命的。我见过一次事故当时为了采集现场直接在生产环境对一个6GB堆的服务执行了dump结果FGC停顿加上堆序列化接口超时警报直接拉满。从此以后我给自己立了规矩生产环境要做堆转储优先走jmap -dump:live并且一定要选业务低峰期如果必须立即采集要提前和业务方确认可接受的最大停顿时间。5.3 低内存环境的替代方案在某些容器环境或者受限权限下jmap可能执行不了比如提示Permission denied或者Unable to open socket file。这时候可以改用jcmdjcmd pid GC.heap_dump /data/dump/heap.hprofjcmd的堆转储功能与jmap的dump基本等价而且权限判断机制略有不同经常出现jmap失败但jcmd成功的情况。此外如果进程已经OOM崩溃jmap已经无从谈起那就只能靠启动参数里的-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump提前兜底让JVM在OOM时自动留下现场。这个参数我强烈建议所有Java服务都加上成本几乎为零关键时刻能救命。5.4 堆转储分析的粗筛方法不想每次都打开重型的图形分析工具时可以先对hprof文件做个粗筛。比如用jhatJDK自带的历史分析工具启动一个Web服务jhat /data/dump/heap.hprof然后浏览器访问http://localhost:7000可以查看对象直方图和引用关系。jhat比较古老、界面简陋但胜在JDK自带、不需要额外安装。追求效率的可以直接用Eclipse MAT的ParseHeapDump命令行批处理工具在服务器上就能完成初步分析输出报告文件再拿回本地细看。我个人习惯是先用jmap -histo看大方向再用jcmd GC.heap_dump导堆最后MAT分析引用链三步走效率最高。6. jinfo核对JVM参数与动态调优6.1 查看参数是否真的生效线上经常遇到一种情况启动脚本里明明写了-Xmx4g但通过监控看到的堆上限却不是4GB写了-XX:MaxMetaspaceSize512m但元空间实际涨到了1GB。这种“配置与事实不符”的问题jinfo一眼就能看穿。jinfo pid不带任何选项的jinfo会输出两大部分System properties系统属性和VM flagsJVM参数。也可以只看VM flagsjinfo -flags pid或者只看某个具体参数jinfo -flag MaxHeapSize pid注意MaxHeapSize的单位是字节输出结果可能是4294967296换算一下才是4GB。我还见过一种情况参数配置在启动脚本里但实际生效的不是脚本里的值而是被框架或部署平台覆盖了。比如某些云平台会通过环境变量或agent注入JVM参数这时候jinfo显示的值才是JVM真正在用的值一切都以jinfo为准。6.2 动态修改参数的边界jinfo还支持在运行中修改部分JVM参数语法jinfo -flag HeapDumpOnOutOfMemoryError pid jinfo -flag MaxHeapFreeRatio40 pid但这里有一条严格边界并非所有参数都支持运行时修改。像堆大小-Xmx、GC收集器选择这类参数是JVM启动时就需要确定的jinfo无法修改。只有被标记为manageable的参数才支持动态调整常见的如HeapDumpOnOutOfMemoryError、MaxHeapFreeRatio、MinHeapFreeRatio、PrintGC、PrintGCDetails等。我自己在紧急情况下用过一次动态开启GC日志jinfo -flag PrintGCDetails pid jinfo -flag PrintGCDateStamps pid配合重定向GC日志的方式在不重启服务的情况下临时打开了GC记录等排查完再关掉。这种操作适合应急但需要注意的是有些参数动态修改后要重启才能完整生效或者修改后会影响JVM的自适应调节策略所以动态调参之后要密切观察监控曲线确认没有引入新的问题。7. jcmd新一代诊断瑞士军刀7.1 为什么要主推jcmdJDK 8之后oracle官方已经在逐步削弱jstack、jmap等独立工具的定位把功能越来越集中到jcmd里。JDK 9模块化之后jmap、jstack的部分底层能力还依赖内部模块在新版本上执行时经常出现警告甚至失败。而jcmd直接和JVM内部接口通信支持的命令列表会随版本自动适配兼容性更好。所以我在JDK 11的环境里已经很少单独用jstack和jmap了基本都是jcmd一把梭。查看jcmd支持哪些指令jcmd pid help这个输出会列出该JVM版本支持的所有命令名称不同版本略有差异。常用的几个我列在下面命令作用Thread.print打印线程转储等价于jstackGC.heap_info打印堆配置与使用情况等价于jmap -heapGC.heap_dump导出堆转储等价于jmap -dumpGC.class_histogram打印类直方图等价于jmap -histoVM.system_properties打印系统属性VM.flags / VM.flags -all打印JVM参数PerfCounter.print打印JVM性能计数器VM.uptime打印JVM运行时长VM.version打印JVM版本GC.run_finalization触发finalizer运行7.2 一个命令批量收集排查信息我在一次完整排查中会按顺序执行这么几条jcmd基本把JVM的当前状态一次性全部拉齐jcmd pid VM.uptime jcmd pid VM.version jcmd pid Thread.print /tmp/dump_thread.txt jcmd pid GC.class_histogram /tmp/dump_histo.txt jcmd pid GC.heap_info jcmd pid GC.heap_dump /data/dump/heap.hprof这份组合的输出足够覆盖绝大多数排查场景。有个小经验Thread.print执行时间略长如果服务线程很多输出文件可能几十MB分析时不要直接cat用grep加上下文行数来看。比如要查某个业务线程池的线程先找到线程名的关键字再grep -A 15看完整栈。8. 实战串讲一次完整的CPU飙升排查8.1 接到告警后的第一轮信息收集某天下午我负责的一个订单服务收到告警CPU使用率从平均15%突然飙升到95%接口超时率同步上升。按照流程我先登录服务器第一轮信息采集如下。ps -ef | grep java top -c很快锁定了PID为31207的订单服务进程CPU占用最高。接着我确认了版本和参数jcmd 31207 VM.version jcmd 31207 VM.flags确认是JDK 8、G1收集器、堆上限8GB。然后抓了第一份线程转储jcmd 31207 Thread.print /tmp/dump1.txt8.2 定位高CPU线程在抓线程转储的同时用top看线程级CPUtop -Hp 31207看到有一个线程CPU占用稳定在400%以上PID为51281。换算十六进制printf %x\n 51281得到c851。在dump文件里搜索grep -A 30 nid0xc851 /tmp/dump1.txt栈顶信息非常明确线程卡在一个JSON序列化方法里。栈里反复出现一个JSON.toJSONString调用而调用来源是某个报表统计的接口。8.3 交叉验证与根因确认光靠一份转储还不能百分百确定我隔了5秒又抓了一份jcmd 31207 Thread.print /tmp/dump2.txt grep -A 30 nid0xc851 /tmp/dump2.txt对比发现这个线程在连续两份快照里都停留在相同的JSON序列化栈帧上说明不是在等待IO而是在持续执行CPU密集的序列化计算。再看GC情况jstat -gcutil 31207 1000 5YGC频率略有上升但老年代稳定排除了内存和GC导致CPU飙升的可能。结合代码排查最终确认问题来自一个详情接口最近版本里为支持新前端展示在循环内反复调用JSON序列化每次序列化的对象里还包含大量冗余字段数据量大时CPU消耗急剧放大。8.4 处置与验证定位根因后当时线上的临时处置是联系业务方关闭该报表接口的入口CPU很快回落到正常水平。正式修复则是优化序列化逻辑去除冗余字段并在循环外统一构建返回对象。修复上线后我再观察了一段时间的jstat输出YGC频率平稳CPU曲线恢复正常。这个案例一方面说明jstack和jstat配合就能解决高CPU根因定位另一方面也提醒我们CPU飙升未必是JVM有问题很多时候是业务代码的算法和数据量问题工具只是帮你快速把范围缩到具体线程和栈帧真正的答案还是在代码里。9. 常见问题速查表与避坑心得9.1 高频问题与处理速查症状首选工具关键命令排查要点找不到Java进程jps / jcmdjps -l -v用户身份不匹配时用ps -ef确认CPU高但不知道谁干的top jstacktop -Hp ; printf %x; grep nid连续抓两份对比排除正常波动接口卡顿疑似线程池耗尽jstackjcmd Thread.print重点看线程状态分布BLOCKED多说明锁竞争内存缓慢上涨jstatjstat -gcutil 1000 10看老年代趋势FGC后是否回落OOM要留现场启动参数-XX:HeapDumpOnOutOfMemoryError所有Java服务建议默认开启堆转储打不开jcmdjcmd GC.heap_dump确认JDK版本与工具版本匹配jmap/jstack权限不足sudo / jcmdsudo jcmd Thread.print与进程启动用户一致GC参数没生效jinfojinfo -flags以jinfo实际输出为准9.2 几个值得记住的踩坑细节第一个坑是jstack的输出乱码或内容不完整。低版本JDK在部分中文字符环境下线程名或业务信息里包含中文时输出可能乱码。可以加上-encoding参数指定编码或者等JVM初始化完成后再抓取。更常见的是抓取时机不对——我见过有人在JVM启动十几秒内就jstack结果只能看到少量启动线程误判为“所有业务线程都没了”实际上业务还没起来。第二个坑是多个Java进程共用同一用户时jmap/jstack会报错。这通常是因为JVM进程间的通信文件冲突或权限问题。解决办法是用sudo切换或者通过jcmd -l确认目标进程的准确PID再操作。第三个坑是JDK 9以后jmap -heap的信息不再那么直观。JDK 9对jmap的输出格式做了调整有些老脚本解析jmap输出会失败。这种环境建议优先用jcmd GC.heap_info替代输出结构更稳定。第四个坑是Full GC前后抓jstat对比时时间点要卡准。JVM的FGC时间窗口很短如果采样间隔太长可能错过关键变化。遇到FGC频繁的场景我会把jstat间隔缩短到200ms连续采样20次以上基本能把FGC前后的内存变化过程完整记录下来。9.3 我的日常运维工具箱配置最后分享一个我长期使用的“工具箱”配置。我会在每台Java服务器的部署目录下放一个脚本叫java-diagnose.sh内容大致是接收PID参数自动执行jcmd采集线程转储、GC统计、堆信息、系统属性全部打包成带时间戳的目录。这样平时出问题只需要执行一行脚本就能完成第一轮现场信息采集不用临时一个一个命令敲。脚本逻辑不复杂核心就是串起上面讲到的命令然后加上df -h检查磁盘、top -Hp记录热点线程。等出了事再临时想命令人一紧张特别容易漏这漏那提前备好脚本能帮你从容很多。个人这几年下来越来越觉得排查Java线上问题的核心不是会背多少命令而是脑子里有一条清晰的路径先定位进程再看内存和GC趋势然后抓线程快照定位代码层问题最后用堆转储佐证内存类问题。这一套流程走下来绝大多数问题都能在十分钟内圈定范围。命令本身都很简单真正的功力在于你知道什么时候该用哪个、输出该怎么解读、采集动作对业务有多大影响。把这些基本功练扎实了比装多少花哨的监控工具都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑