资讯动态

jstat命令详解:从JVM监控到Full GC排查实战

发布时间:2026/10/20 23:55:40 来源:尧图企业网站定制
排查线上Java服务问题时我最常敲的第一条命令就是jstat。别的工具要么太笨重要么得等图形界面加载jstat属于那种“轻到没存在感、但关键时刻能救命”的工具。它是JDK自带的标准工具在bin目录里就能找到专门用来监控JVM的类加载、编译和GC行为尤其是GC的频率、停顿和堆内存使用情况几分钟就能把一台即将崩溃的Java进程从“症状”定位到“病灶”。这篇博文我把jstat从头到尾掰开讲清楚包含命令格式、输出字段、真实排查案例和一堆踩出来的坑适合刚接触JVM调优的新手也适合一直凭感觉排查问题的老同学拿来对齐细节。1. 为什么要用jstat它是JVM观测的最小切口1.1 JVM问题排查时的“第一手现场”做Java后端的人大概率遇到过这种场景服务突然变慢、接口超时、CPU飙高或者更严重的——老年代快满了、Full GC一打就几十秒。这时第一步不是看业务日志而是先确认JVM本身是不是健康。jstat的价值就在这个节点体现出来它能在不连接远程端口、不挂代理、不装额外组件的前提下直接对本地JVM进程做实时采样拿到GC次数、GC耗时、各内存区块当前用量等关键数据。我以前处理过一个典型的假死案例某个服务每隔几分钟就停顿一次每次停顿将近10秒。业务日志看不出任何异常数据库也正常。我登录服务器直接敲jstat -gcutil 进程号 1000立即看到老年代使用率从40%飙到99%紧接着触发一次Full GC停顿时间4800毫秒。这个现象一下子就锁定了方向——不是某些线程死锁也不是数据库慢查询而是老年代内存膨胀导致的频繁Full GC。后面再配合jmap看堆内对象分布问题才完全浮出水面。整个过程里jstat是第一个把真相指给我们的工具。1.2 适合谁、解决什么问题jstat适合这么几类人刚入行的Java开发想搞懂GC到底怎么发生的运维同学想在故障发生时快速判断是不是JVM层面的问题技术负责人想在压测过程中实时盯内存和GC趋势。它解决的核心问题可以归纳成三个一是确认GC是否过于频繁二是确认GC停顿是否过长三是确认堆内存和非堆内存的用量是否异常。这三个问题覆盖了绝大多数JVM运行时的性能故障。不过你也要知道它的边界。jstat不会告诉你“是哪个对象占了多少内存”也看不到线程栈更无法分析方法级别的CPU热点。它只负责“块级”观测——像体检报告里的血压和心率能发现问题苗头但要做手术级别的定位还得靠jmap、jstack、jcmd这些兄弟工具配合。2. 命令格式与常用参数先把工具箱里的尺子认全2.1 jstat基本语法jstat的完整命令格式是jstat -option [-t] [-hlines] vmid [interval [count]]解释一下各个部分-option要执行的统计项比如-gc、-gcutil、-class、-compiler等后面会逐个说明。-t在输出中显示从JVM启动到当前的时间戳单位秒想观察趋势时非常有用。-hlines每输出多少行后重新打印一遍表头比如-h10就是每10行刷新一次表头方便看大量数据时不迷路。vmid虚拟机进程IDLinux下用ps -ef | grep java或者jps -l就能拿到。interval采样间隔单位毫秒。比如1000就是一秒打一次。count采样次数不填就一直打印直到手动停止。命令行实际用起来大概是这样的jstat -gcutil 12345 1000 10意思是对进程12345每隔1秒输出一次GC统计共输出10次后自动停止。2.2 常用option选项一表看懂jstat的功能选项不少但日常用得上的其实集中在下面几个选项输出内容典型使用场景-class类加载/卸载数量与总空间排查元空间溢出、类加载器泄漏-compilerJIT编译器编译统计观察方法编译耗时、编译失败-gc各内存区块容量与用量、GC时间精细查看堆与元空间细节-gccapacity各内存区块的容量上下限判断扩容是否达到上限-gcutil各内存区块使用比例、GC时间快速总览内存与GC健康度-gccause同-gcutil并额外显示上次GC原因定位Full GC触发原因-gcnew新生代详细GC统计观察Minor GC对新生代影响-gcold老年代详细GC统计观察老年代容量与使用率-gcmetacapacity元空间容量统计排查元空间扩容与溢出这里面我用的频率最高的是-gcutil和-gccause。前者一行就能扫出S0、S1、E、O、M五块区域的使用率百分比后者还会多告诉你最近一次GC是什么原因触发的排查线上Full GC时这个字段非常关键。2.3 采样间隔与次数怎么定采样设计看起来简单但里面有不少讲究。如果你只是想看一眼当前状态直接jstat -gcutil 进程号就行了默认只输出一次。如果要观察GC的连续性比如压测时盯内存增长曲线我习惯用1000毫秒间隔、连续采样几十次甚至不限制次数。间隔太短比如100毫秒会频繁触发安全点检测和对进程的系统调用虽然jstat开销很小但高并发服务上还是别用过度间隔太长比如30秒一次可能错过关键GC事件。一个比较稳妥的做法是“先快后慢”先用1秒间隔连续采10次看清当前走势再拉长到5秒间隔持续观察几分钟。这个思路和排查问题时的“先定性、再定量”一致。2.4 常见报错处理用jstat最常见的报错无非这几种process not found进程号写错了或者Java进程已经退出。Unable to open socket file目标进程不是JVM进程或权限不够。Unrecognized option选项名拼错了比如把-gcutil写成了-gcUtils。遇到第一和第二种先确认进程是否存在必要时用jps -l重新拿一次进程号。权限问题在容器环境里尤其常见运行用户不一致时直接sudo -u 用户切换身份再执行。3. 输出字段逐个拆解数字背后的完整图谱3.1 从一次实际输出讲起先看一条真实输出我用-gcutil采样得到的S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 68.20 47.80 95.32 90.10 1834 32.400 12 48.600 81.000新手最容易懵的就在这里一堆缩写到底谁是谁别急逐个剥开。S0、S1Survivor区0和Survivor区1的使用率百分比。用来观察对象在新生代里的存活与晋升行为。EEden区使用率百分比新生代对象绝大多数先在这里分配。O老年代使用率百分比这是判断Full GC可能性的最重要指标。M元空间使用率百分比对应JDK8之后的Metaspace存放类元数据。CCS压缩类空间使用率百分比。YGCYoung GCMinor GC发生次数。YGCTYoung GC累计消耗的秒数。FGCFull GC发生次数。FGCTFull GC累计消耗的秒数。GCTGC总耗时等于YGCT FGCT。如果加上-t参数最前面还会多出一列从JVM启动到现在的秒数。这列数据用来算GC频率的“斜率”非常有用。比如观察两次采样之间的YGC差值配合时间差值就能算出每分钟多少次Young GC判断新生代是不是太小或者对象分配速率太高。3.2-gc输出里的容量字段-gc比-gcutil多出很多容量字段同样以一次输出为例S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 2048.0 2048.0 0.0 0.0 16384.0 11184.7 40960.0 19566.4 9780.0 9323.5 1024.0 924.1 1834 32.400 12 48.600 81.000S0C、S1CSurvivor区当前容量单位KB。S0U、S1USurvivor区当前使用量。EC、EUEden区容量和使用量。OC、OU老年代容量和使用量。MC、MU元空间容量和使用量。CCSC、CCSU压缩类空间容量和使用量。这套数据能帮你判断扩容行为。比如OC从40MB一路涨到90MB说明老年代在动态扩容通常是对象不断晋升导致的如果OU一直贴着OC跑老年代使用率居高不下就要考虑堆参数设置或者对象回收问题了。3.3 类加载与编译相关的输出-class的输出比较简洁Loaded Bytes Unloaded Bytes Time 12345 23456.7 67 123.4 5.67Loaded已加载类的数量。Bytes加载类占用的空间。Unloaded已卸载类的数量。Time类加载和卸载累计耗时。这个统计在排查重复部署、热部署导致类加载器泄漏时很有用。正常来说一个健康应用启动几小时后类加载数量应该趋于稳定。如果你发现Loaded数量持续增长且Unloaded一直不涨那几乎可以断定有动态生成类的代码在运行比如频繁的反射代理、CGLIB或者Groovy动态脚本这类问题拖久了就是Metaspace溢出。-compiler输出的是JIT编译数据Compiled Failed Invalid Time FailedType FailedMethod 3210 0 0 123.45 0看Failed和Invalid这两列就够了。正常情况下失败数应该是0或者极小。如果失败次数一直在涨多半是代码触发了编译器bug或者字节码异常这时可以配合-XX:PrintCompilation进一步深挖。4. 实战演练一次线上Full GC的逐步定位过程4.1 场景背景有次一个接口服务在发布新版本后出现周期性“卡顿”每次卡顿持续5到8秒接口成功率明显下降。第一反应看GC日志但服务没开GC日志参数日志文件压根不存在。也没接监控平台唯一能用的就是命令行工具。这时候jstat成了唯一的突破口。先用jps拿到进程号jps -l输出大概是34567 /opt/app/release/xxx-service.jar这样的格式。拿到进程PID后我立即执行了第一条命令jstat -gcutil 34567 1000 104.2 关键数据的两次采样对比第一次采样的几个关键数据是这样的S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 85.20 40.10 95.10 88.30 5230 45.200 3 8.500 53.700看起来还正常YGC次数约5230次FGCT只有8.5秒。但没过几轮观察数据开始剧烈变化S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 12.30 99.80 95.20 88.40 5240 46.200 4 18.200 64.400两个关键异常点出现了老年代O从40%直接拉到99.8%FGC次数从3涨到4单次Full GC耗时从约2.8秒跳到9.7秒停顿猛增。从这里基本能断定系统正在经历高频Full GC而且每次Full GC停顿严重。为什么老年代会在短时间内塞满答案还得往下挖。4.3 用-gccause确认触发原因并锁定排查方向为了确认触发Full GC的最直接原因执行了jstat -gccause 34567 1000 3输出里多了一项LGCC显示Metadata GC Threshold。这一下真相清晰了一半——触发的不是空间分配失败而是元空间快要达到阈值触发的Full GC。再看M那一列元空间使用率一直在95%左右徘徊明显偏高了。到这里方向就从“老年代膨胀”修正为“元空间压力过大导致Full GC而Full GC顺便回收老年代”。原因修正后排查重点转到“什么代码在疯狂产生类元数据”上。结合项目背景最后定位到新版本里引入的一个动态脚本组件在每次请求时都动态编译脚本文件导致类元数据不断累积。修复方式是改为脚本缓存复用问题随即消失。这个案例有意思的地方在于如果一开始只盯着老年代看可能去调整堆大小参数不仅白调还会掩盖真正的问题。jstat最值钱的用法不是让你看见“哪块满了”而是通过-gccause这类细节信息把触发原因也一并摆出来帮你少走弯路。5. 常见问题与避坑技巧从字段误读到参数陷阱5.1 频繁Full GC但老年代很小可能不是堆的问题有一次我看到一个进程的FGC猛涨但老年代使用率O始终只有20%上下。按常理Full GC触发要么是老年代满了要么是元空间满了要么是显式调用System.gc()。打印GC原因之后发现是Metadata GC Threshold元空间触顶了。这种情况如果只调-Xmx是治不了本的得调-XX:MaxMetaspaceSize或者更本质地解决类加载器泄漏问题。老话说得好“堆满了不等于就是堆的错。”jstat输出的每个区块都要结合起来看不能只看老年代一个数字。5.2 元空间使用率一直95%以上但Full GC不多元空间使用率高不一定立刻导致Full GC。它有一个阈值机制每次扩容到当前阈值附近就会触发一次回收尝试如果回收后还是不够继续扩容。你看到的M百分比显示的是当前容量下的使用情况不是说超过95%就一定会出问题。但如果你看到M长期高位同时FGC次数还在增长那就要警惕了。一个常用的排查组合是先用-gcmetacapacity看元空间容量是否已经被撑到MaxMetaspaceSize上限如果已经顶满且使用率还居高不下那问题基本坐实。5.3 单次采样容易被瞬时波动误导jstat默认只打印一次很多时候恰恰是那一次把你看晕。比如Eden区使用率突然99%你以为要GC了结果下一秒Minor GC发生后跌到5%。单次采样只能反映一个瞬间趋势请用连续多次采样观察。建议盯对象的时候用这个组合jstat -gcutil 进程号 1000 30一次打30行配合-h10让表头每隔10行重新显示既能看到变化趋势也不会视觉疲劳。我在压测环境里基本上都是这种方式稳得很。5.4 容器环境里的权限与PID坑容器化部署最常见的坑是在宿主机上直接ps -ef | grep java看到的PID不是容器内的实际Java PIDjstat连过去会直接报错。解决方法是进入容器内部在容器里执行jps拿PID或者使用/proc机制找到对应用户的Java进程。另外如果容器以非root用户运行而你的登录用户在宿主机上权限不够同样会导致Unable to open socket file。这跟jstat本身没关系是操作系统权限控制导致的。万能的处理法是换成与JVM进程相同的系统用户再执行命令。5.5 -gcutil百分比与-gc容量字段要配合阅读-gcutil适合快速过一眼-gc适合看真实容量。有个容易忽略的细节Survivor区的百分比0%不代表没有对象可能只是S1完全没用到老年代60%使用率在堆很大时不代表空间不够得结合OC和OU算绝对值。有一次一个服务堆设置成16GB老年代60%意味着用了9.6GB看着比例不高但其实已经接近物理机的可用内存上限了。光看百分比容易误判容量字段才是硬指标。6. jstat与兄弟命令的组合打法6.1 先jstat定性再jmap/jstack定量jstat擅长告诉你“JVM当前哪儿不对劲”但不会告诉你“是哪个代码干的好事”。所以我的排查链路一般是jstat -gcutil看GC健康度。jstat -gccause看触发原因。jmap -dump:live,formatb,fileheap.bin 进程号拉堆快照。用MAT或VisualVM分析大对象和引用链。如果存在明显的线程停顿或死锁嫌疑再jstack看线程状态。这是一条非常成熟的路线。没有jstat打前站直接dump堆很可能把一堆正常数据搬下来浪费时间还抓不住关键时刻。6.2 写一个轻量的采集脚本做趋势记录jstat本身是无状态的命令但把它写进循环就能变成简易监控。我常用下面这个脚本快速抓一段时间的趋势#!/bin/bash PID$1 INTERVAL1000 COUNT60 TIMESTAMP$(date %Y%m%d_%H%M%S) OUTFILE/tmp/jstat_${PID}_${TIMESTAMP}.log jstat -gcutil -t $PID $INTERVAL $COUNT $OUTFILE echo log saved to $OUTFILE跑一分钟把日志拉回来分析改动最小不用给目标JVM加任何参数在排查现场非常实用。6.3 数据尘埃落定之后的判断方法论数据都拿到手之后判断的经验公式我总结成三句话Young GC频率过高优先看Eden区容量和对象分配速率Full GC频率过高优先看老年代容量、晋升速率和元空间是否异常GC停顿时间长优先看堆大小和GC算法类型。jstat的输出全部围绕这三个维度展开所以每次采样脑子里都要把数据映射到这三问里。排查得多了你会发现大部分问题都在这三句话的组合范围内。

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

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

免费获取报价 →
↑